HTTP代理服务器的实现

2023-05-16

接下来都是我对HTTP代理服务器的理解。

HTTP代理服务(proxy server) 器就是客户端也是服务端,是一个事务处理的中间人,就像下图所展示的一样,
这里写图片描述
图片来源于《HTTP权威指南》

代理服务器的作用有很多,例如:儿童过滤器、文档访问控制、安全防火墙、web缓存等等。

以下,我用最基础的代码写一个简易的HTTP代理服务器,最后还有相关的解说,有助于更容易理解HTTP代理服务器的机制。

package testproxy;
import java.lang.reflect.Constructor;
import java.net.*;
import java.io.*;
public class TextHttpProxy extends Thread {

    //操作实现代理服务器的类
    static public int RETRIES = 5;
    //在放弃之前尝试连接远程主机的次数
    static public int PAUSE = 5;
    //在两次连接尝试之间的暂停时间
    static public int TIMEOUT = 50;
    //等待Socket输入的等待时间
    static public int BUFSIZ = 1024;
    //输入的缓冲大小
    static public boolean logging = false;
    //是否要求代理服务器在日志中记录所有已传输的数据
    static public OutputStream log = null;
    //默认日志例程将向该OutputStream对象输出日志信息
    protected Socket socket;
    // 传入数据用的Socket
    static private String parent = null;
    //上级代理服务器
    static private int parentPort = -1;
    //  用来把一个代理服务器链接到另一个代理服务器(需要指定另一个服务器的名称和端口)。  
    static public void setParentProxy(String name, int port) {

        parent = name;
        parentPort = port;

    }
    public TextHttpProxy(Socket s) {

        //创建一个代理线程
        socket = s;
        start();
        //启动线程

    }
    public void writeLog(int c, boolean browser) throws IOException {

        //写日志
        log.write(c);

    }
    public void writeLog(byte[] bytes, int offset, int len, boolean browser)
            throws IOException {

        //循环写日志
        for (int i = 0; i < len; i++)
            writeLog((int) bytes[offset + i], browser);

    }
    // 默认情况下,日志信息输出到控制台或文件
    public String printLog(String url, String host, int port, Socket sock) {

        java.text.DateFormat cal = java.text.DateFormat.getDateTimeInstance();
        System.out.println(cal.format(new java.util.Date()) + " - " + url + " "
                + sock.getInetAddress() + "\n");
        return host;

    }
    public void run() {

        // 执行操作的线程
        String line;
        String host;
        int port = 80;
        //默认端口为80
        Socket outbound = null;
        //每次请求都会创建一个新的线程
        try {

            socket.setSoTimeout(TIMEOUT);
            //设置超时时间
            InputStream is = socket.getInputStream();
            //创建输入流
            OutputStream os = null;
            try {

                line = "";
                // 获取请求行的内容
                host = "";
                int state = 0;
                boolean space;
                while (true) {

                    //无限循环
                    int c = is.read();
                    //读取输入流的信息
                    if (c == -1)//没有读取信息
                        break;
                    if (logging)
                        writeLog(c, true);
                    //将信息写入日志
                    space = Character.isWhitespace((char) c);
                    //判断是否为空白字符
                    switch (state) {

                    //判断状态
                    case 0:
                        if (space)
                            continue;
                        //跳过本次循环
                        state = 1;
                        //更改状态
                    case 1:
                        if (space) {

                            state = 2;
                            continue;
                            //跳过本次循环

                        }
                        line = line + (char) c;
                        //添加读取的信息
                        break;
                    case 2:
                        if (space)
                            continue; 
                        // 跳过空白字符
                        state = 3;
                        //更改状态
                    case 3:
                        if (space) {

                            //如果是空白字符
                            state = 4;
                            //更改状态
                            String host0 = host;
                            //取出网址
                            int n;
                            n = host.indexOf("//");
                            //获取网址(不包括协议)
                            if (n != -1)
                                //没有找到
                                host = host.substring(n + 2);
                            n = host.indexOf('/');
                            if (n != -1)
                                //没有找到/
                                host = host.substring(0, n);
                            n = host.indexOf(":");
                            // 分析可能存在的端口号
                            if (n != -1) {

                                //没有找到:
                                port = Integer.parseInt(host.substring(n + 1));
                                host = host.substring(0, n);

                            }
                            host = printLog(host0, host, port, socket);
                            //获得网站域名

                            if (parent != null) {

                                host = parent;
                                port = parentPort;

                            }
                            int retry = RETRIES;
                            while (retry-- != 0) {

                                try {

                                    outbound = new Socket(host, port);
                                    //创建连接对象,通向目标服务器
                                    break;

                                } catch (Exception e) {

                                    System.out.println("无法创建连接:"+e.getMessage());

                                }
                                Thread.sleep(PAUSE);
                                //设置线程等待

                            }
                            if (outbound == null)
                                break;
                            outbound.setSoTimeout(TIMEOUT);
                            //设置超时时间,防止read方法导致的组赛
                            os = outbound.getOutputStream();
                            //获得输出流对象
                            os.write(line.getBytes());
                            //将信息写入流
                            os.write(' ');
                            os.write(host0.getBytes());
                            //将信息写入流
                            os.write(' ');
                            writeInfo(is, outbound.getInputStream(), os, socket
                                    .getOutputStream());
                            //调用方法将信息写入日志,套接字数据的交换
                            break;

                        }
                        host = host + (char) c;
                        break;

                    }

                }

            } catch (IOException e) {

            }

        } catch (Exception e) {

        } finally {

            try {

                socket.close();
                //释放资源

            } catch (Exception e1) {

            }
            try {

                outbound.close();

            } catch (Exception e2) {

            }
        }
    }
    void writeInfo(InputStream is0, InputStream is1, OutputStream os0,
            OutputStream os1) throws IOException {

        //读取流中信息写入日志
        try {

            int ir;
            byte bytes[] = new byte[BUFSIZ];
            //创建字节数组,大小:1024
            //也是定影socket缓冲区的大小
            while (true) {

                try {

                    if ((ir = is0.read(bytes)) > 0) {

                        //判断读取输入流的信息
                        os0.write(bytes, 0, ir);
                        //将读取的数据写入输出流对象中

                        if (logging)
                            writeLog(bytes, 0, ir, true);
                        //写入日志

                    } else if (ir < 0)
                        //读取完毕
                        break;
                } catch (InterruptedIOException e) {

                    //捕获中断IO流异常

                }
                try {

                    if ((ir = is1.read(bytes)) > 0) {

                        //判断读取输入流的信息
                        os1.write(bytes, 0, ir);
                        //将读取的数据写入输出流对象中
                        if (logging)
                            writeLog(bytes, 0, ir, false);
                        //写入日志

                    } else if (ir < 0)
                        //读取完毕
                        break;

                } catch (InterruptedIOException e) {

                    //捕获中断IO流异常

                }
            }
        } catch (Exception e0) {

            //捕获异常
        }
    }


    static public void proxyStart(int port, Class<TextHttpProxy> clobj) {

        ServerSocket serverSocket;
        try {

            serverSocket = new ServerSocket(port);
            //根据端口创建服务器端Socket对象
            while (true) {

                Class[] objClass = new Class[1];
                //创建类数组,大小为1
                Object[] obj = new Object[1];
                //创建对象数组,大小为1
                objClass[0] = Socket.class;
                //添加Socket类
                try {

                    Constructor cons = clobj.getDeclaredConstructor(objClass);
                    //创建代理服务器实例
                    obj[0] = serverSocket.accept();
                    //挂起等待客户的请求
                    cons.newInstance(obj); 
                    // 创建TextHttpProxy或其派生类的实例  创建传入类

                } catch (Exception e) {

                    Socket socket = (Socket) obj[0];
                    //对象强制转换
                    try {

                        socket.close();
                        //释放资源

                    } catch (Exception ec) {

                    }

                }

            }

        } catch (IOException e) {

        }
    }
    static public void main(String args[]) {

        System.out.println("HTTP代理服务器已经成功启动!");
        TextHttpProxy.log = System.out;
        //日志信息输出到控制台
        TextHttpProxy.logging = false;
        TextHttpProxy.proxyStart(9080, TextHttpProxy.class);

        //调用方法
    }

}

代码解说:

开启一个服务器Socket 监听指定端口的请求,同时,代理服务器挂起等待客户端的请求。服务器的Socket监听到连接请求时,则开启一个新的线程处理这个连接请求,服务器的Socket 再次进入监听状态。
在连接线程时,代理服务器接受来自客户端的请求,并执行操作的线程,设置超时时间,解析客户端Host头域里面的值,获取目标web服务器地址,分析可能存在的端口号,建立socket将请求发送到远程服务器,然后将响应报文转发回原socket。最后把 socket 关闭,线程销毁。

写一个简单的main方法来进行代理服务器测试,并将日志信息显示在控制台。

代码解说图:
这里写图片描述

以下图片是测试效果:

使用firefox浏览器,手动设置代理服务器
这里写图片描述

运行代码,开启代理服务器,输入HTTP协议的URI,如图

这里写图片描述

能进入这个网站,说明你的代理成功了!

在控制台会输出以下信息
这里写图片描述

在这个实现HTTP代理服务器的过程中,socket 是非常重要的,它也叫做套接字。如下是它的作用:

socket (套接字)之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认

服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

如果不太懂HTTP和socket之间的联系,那就用传说中的比喻来说说:HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。

如果以后懂得更多,会及时补充~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

HTTP代理服务器的实现 的相关文章

  • Tomcat 7 停止接收 HTTP 请求

    我有一个Tomcat 7接收大量数据的服务器GET 要求 这种方法在一段时间内效果很好 然后突然停止工作 7 8 小时后 当它停止工作时 我收到此错误 五月 06 2015 12 47 58 AM org apache coyote htt
  • 静默地将 api 资源移动到另一个 url

    我已经用 WepApi 2 编写的 api 与主网站紧密结合 我决定将其与另一个网络应用程序解耦 以使事情更加隔离 我遵循了这样的步骤 将所有 API 控制器提取到另一个项目 创建属性以将当前使用旧 URL 的所有用户重定向到新 URL 由
  • Java HttpURLConnection:内容长度计算

    我目前正在为 bitbucket issues RESTful API 开发一个库 我取得了很大的进步 现在我要解决这个部分更新问题 http confluence atlassian com display BBDEV Issues Is
  • 有没有办法测量 Java (Servlet) I/O 流量?

    我尝试做的是使用以下代码实现 servlet 过滤器 int up request getContentLength if HttpServletRequest request getQueryString null up Math max
  • 从express.js 中删除所有标头

    我正在创建一个页面 其中有一些数据可以由另一个设备解析 我曾经使用 php 执行此操作 但现在将其移至 Node js 我需要从页面中删除所有标题 这样我就只有我的输出 此输出是对 GET 请求的响应 此刻我有 HTTP 1 1 200 O
  • HTTP实时音频流服务器

    作为概念验证 我需要创建一个 HTTP 服务器 该服务器在 GET 请求时应启动连续的非编码 非压缩音频数据流 WAV PCM16 我们假设音频数据是 4096 个随机生成的单声道音频样本块 采样率为 44 1kHz 我应该在 HTTP 响
  • 使用传输编码分块的 HTTP 响应中的最大块大小是多少?

    The w3 org RFC2616 http www w3 org Protocols rfc2616 rfc2616 sec3 html sec3 6 1似乎没有定义块的最大大小 但是如果没有最大块大小 则没有空间用于块扩展 必须有一个
  • Jsoup http 日志记录

    有没有办法记录http请求和响应 我们假设以下请求 Connection Response res Jsoup connect LOGIN URL HERE data user USER pass PASS method Connectio
  • “双点”可以作为 URL 路径部分的一部分吗

    在 URL 中使用父目录双点是否有效且安全 如下例所示 http example com path to file jpg RFC3986 https www rfc editor org rfc rfc3986定义 URI 它描述了路径如
  • 在 Heroku 上获取客户端的真实 IP 地址

    在任何 Heroku 堆栈上 我想获取客户端的 IP 我的第一次尝试可能是 request headers REMOTE ADDR 当然 这是行不通的 因为所有请求都是通过代理传递的 所以替代方法是使用 request headers X
  • 您可以从 AuthorizeAttribute 返回 HTTP 响应而不引发异常吗?

    我在各种控制器上使用 AuthorizeAttribute 可能需要根据请求本身的某些属性返回 403 或 429 请求过多 我完全在自定义 OnAuthorization 实现中实现了它 然后在必要时抛出一个带有适当响应代码的新 Http
  • HTTP 和 HTTPS iframe

    我正在创建一个小部件 我想允许其他人使用它 这iframe通过 HTTP 加载 但我想允许用户通过 HTTPS 登录 即通过 SSL 发送登录请求 同源策略中允许这样做吗 即 场景是用户可以将我的 JavaScript 集成到他们的网站 小
  • 由于请求的资源上不存在“Access-Control-Allow-Origin”标头,无法获取与 Axios 的链接请求

    我正在尝试使用 cryptocompare api 来获取 axios 的 coindata 列表 但我不知道如何解决这个问题 我相信这是一个 CORS 问题 但我不确定 完整错误如下 加载失败https www cryptocompare
  • 是否可以修改 $_SESSION 变量?

    恶意用户是否可以将 SESSION 在 php 中 变量设置为他想要的任何值 很大程度上取决于您的代码 有一点非常明显 SESSION username REQUEST username
  • Access-Control-Allow-Origin值跨站缓存

    我正在尝试编写一个 nginx 配置来处理 http 和 https 上的两个站点 只要客户端从不访问这两个站点 它似乎就可以工作 但如果它们这样做 就会出现缓存 跨站点问题 Allow cross origin location eot
  • 编写每个处理程序中间件

    我希望从处理程序中提取一些重复的逻辑 并将其放入一些每个处理程序的中间件中 特别是 CSRF 检查 检查现有会话值 即身份验证或预览页面 等 我读了关于此的几篇文章 http justinas org writing http middle
  • 如何使用 Ruby on Rails 3 检查 HTTP 请求的“Content-Length”字段?

    我正在使用 Ruby on Rails 3 在我的视图文件中我有以下代码 为了避免服务器过载 我会在服务器接收上传文件之前检查上传文件的大小 这是因为 按下表单的提交按钮 服务器会先完整接收文件 然后再检查文件 我知道一个HTTP 请求有标
  • 网站(Google 和/或您)应如何处理 Accept-Language 标头?

    很长一段时间以来 我对谷歌在以下情况下的行为并不满意 并且在无意中注意到之后80 其他人 https stackoverflow com questions 1011167 what are common ui misconceptions
  • Python Requests 库重定向新 url

    我一直在浏览 Python 请求文档 但看不到我想要实现的任何功能 在我的脚本中我设置allow redirects True 我想知道该页面是否已重定向到其他内容 新的 URL 是什么 例如 如果起始 URL 为 www google c
  • 如何在 PHP 中使用 file_get_contents 获取图像的 MIME 类型

    我需要获取图像的 MIME 类型 但我只有图像的正文file get contents 是否有可能获取 MIME 类型 是的 你可以这样得到它 file info new finfo FILEINFO MIME TYPE mime type

随机推荐

  • APM_ArduCopter源码解析学习(四)——IMU

    APM ArduCopter源码解析学习 xff08 四 xff09 IMU 前言一 system cpp 1 1 无人机内部初始化1 2 Copter init ardupilot 1 3 Copter startup INS groun
  • 查看当前系统的glibc版本

    有时我们经常需要查看当前系统的glibc版本 xff0c 可以这样查看 lib libc so 6 有时 lib x86 64 linux libc so 6 把这个文件当命令执行一下 为什么这个库可以直接run呢 xff1f 原来在lib
  • Java多线程学习三:有哪几种实现生产者消费者模式的方法

    我们先来看看什么是生产者消费者模式 xff0c 生产者消费者模式是程序设计中非常常见的一种设计模式 xff0c 被广泛运用在解耦 消息队列等场景 在现实世界中 xff0c 我们把生产商品的一方称为生产者 xff0c 把消费商品的一方称为消费
  • 如何在 Ubuntu 中管理和使用逻辑卷管理LVM

    在我们之前的文章中 xff0c 我们介绍了什么是 LVM 以及能用 LVM 做什么 xff0c 今天我们会给你介绍一些 LVM 的主要管理工具 xff0c 使得你在设置和扩展安装时更游刃有余 正如之前所述 xff0c LVM 是介于你的操作
  • 如何获取本地和远程主机的IP及MAC地址

    这篇文章 xff0c 我们不准备大规模的讨论技术问题 只是向大家介绍一下我们将如何获得一台主机的IP地址 在Win32 API中我们可以使用NetWork API完成这项工作 xff0c 但是在 Net平台下我们应当如何做呢 xff1f 其
  • 聊聊java中一些减少if-else 的编码方式!

    01 前言 前段时间在阅读别人所写的代码的时候 发现其中一些业务相关的方法体内 出现了比较多的if else语句多层嵌套的情况 首先我个人不是不提倡写if else语句 不得不说 很多时候 在写某些逻辑 使用if else 去做判断 代码看
  • 如何用简单方法推导正弦函数的和角公式: sin(α+β)=sinαcosβ+cosαsinβ ?

    问题 xff1a 看2014年湖北省高考理科数学题 xff0c 选择题第6题 xff1a 这道题目答案是C xff0c 组是正交函数 xff0c 组不是正交函数 可以用数形结合方式 xff0c 快速做出判断 详细解析如下 分析 xff1a
  • Http头部参数:Authorization

    项目uu约优中 xff0c 用到了头部Authorization 当时传递的参数也是后端返回的20位字符 项目sxaik中 xff0c http请求的头部传递Authorization xff0c 值为32位小写字符 xff0c 不确定是m
  • 高性能计算

    信息时代的硬件芯片和存储器价格以摩尔定律的形式下降 xff0c 可是现在处理的数据量也越来越大 我们先以cocoa编程为例 xff0c 然后再结合网格计算 云计算 xff0c 综合对最新的高性能计算技术作介绍 使用 runloop 在coc
  • @Documented注解的作用

    目录 在哪里用到了 96 64 Documented 96 注解 xff1f 那么 64 Documented的作用是什么 xff1f 在哪里用到了 64 Documented注解 xff1f 64 Documented是元注解 xff0c
  • 球的表面积公式是怎么推导出来的?

    球的体积公式的推导 球的表面积公式是 xff1a 证明方式一 xff1a 体积求导 基本思路 xff1a 可以把半径为R的球 xff0c 从球心到球表面分成n层 xff0c 每层厚为 r n xff0c 像洋葱一样 半径获得增量是 r xf
  • ViewBinding简单使用

    官方文档 xff1a https developer android google cn topic libraries view binding hl 61 zh cn java 在app module下的build gradle文件中
  • Android广播实现进程间通信,很简单

    应用A发送广播 xff1a span class token keyword public span span class token keyword class span span class token class name MainA
  • 下载JDK8 JVM源码

    性子急的可以直接看快速下载步骤 xff1a 目录 详细步骤快速下载步骤 详细步骤 打开openJDK官网 xff1a https openjdk org 找到左侧的Mercurial xff0c 点击进入新界面 选择jdk8 xff0c 点
  • Git查看分支的创建人

    开发小组人多的时候 xff0c 仓库里会有跟多分支 xff0c 需要看下某个分支具体是谁创建的 命令 xff1a git for each ref format 61 39 committerdate 09 authorname 09 re
  • kotlin的this关键字几种用法

    与java不同的是 xff0c 原先MainActivity this这种写法在kotlin中会报错 如下 正确的写法有许多 xff0c 直接就写this也可以识别到 xff0c 如下 xff1a span class token clas
  • kotlin中匿名内部类的写法

    原本java开发安卓常用的setOnClickListener xff0c 用kotlin写 xff0c 也变得五花八门了 span class token keyword var span view span class token op
  • Spring与SpringMVC的区别和联系是啥?

    Spring Spring是一个开源容器框架 xff0c 可以接管web层 xff0c 业务层 xff0c dao层 xff0c 持久层的组件 xff0c 并且可以配置各种bean 和维护bean与bean之间的关系 其核心就是控制反转 I
  • “在XML文件中给代码加注释”请注意注释的位置

    先科普一下eclipse加注释的快捷键 xff1a eclipse中编辑Java文件时 xff0c 注释和取消注释的快捷键都是 xff1a 34 CTRL 43 34 编辑xml文件时 xff0c 注释 xff1a CTRL 43 SHIF
  • HTTP代理服务器的实现

    接下来都是我对HTTP代理服务器的理解 HTTP代理服务 xff08 proxy server xff09 器就是客户端也是服务端 xff0c 是一个事务处理的中间人 xff0c 就像下图所展示的一样 xff0c 图片来源于 HTTP权威指