基于 Gin 的 HTTP 代理上网行为记录 demo

前言: 前端时间写了好几篇使用 Gin 框架来做 HTTP 代理 demo 的文章,然后就想着做一个记录上网行为的小工具,就是简单记录看看平时访问了什么网站(基于隧道代理的,不是中间人代理,所以只能记录去了哪里,不能记录干了什么)。不过因为编译问题一直没有解决,我又不想重新在 Windows 上安装 Golang 的开发环境,所以就把它搁置了。最近正好把那个交叉编译的问题解决了,所以就把这个博客也发出来吧。

一、代码

主要的代码还是前几篇博客已经介绍过的了,我只是加了一个写入 sqlite3 的功能。因为也没有必要有一条一条的写入,所以就是每 100 条写入一次了。程序运行会在当前目录生成一个 net_access.db 的文件,这个就是 sqlite 的数据库文件。这里只有一个记录访问了什么网站的表,本来还想着做一个 block_list 的表,阻止访问某些网站的(就是不给它建立连接),不过放弃了。

package main

import (
	"bufio"
	"io"
	"log"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

type ReqRecord struct {
	ID        uint      `gorm:"primaryKey,column:id"`
	CreatedAt time.Time `gorm:"column:time"`
	Schema    string    `gorm:"column:schema"` // http/https
	Host      string    `gorm:"column:host"`
	Port      string    `gorm:"column:port"`
}

const (
	RPOXY_SERVER  = "CrazyDragonHttpProxy"                                                             // it is just a kidding, but Only HTTP!
	TUNNEL_PACKET = "HTTP/1.1 200 Connection Established\r\nProxy-agent: CrazyDragonHttpProxy\r\n\r\n" // Don'e USE `` to surround a protocl strng, DAMN!!!
	BUFFER_SIZE   = 100
)

var (
	proxyHttpClient = http.DefaultClient
	DBConn          *gorm.DB
	recordChan      chan *ReqRecord
)

func init() {
	var err error
	DBConn, err = gorm.Open(sqlite.Open("net_access.db"), &gorm.Config{})
	if err != nil {
		panic("failed to connect database")
	}
	err = DBConn.AutoMigrate(&ReqRecord{})
	if err != nil {
		panic(err)
	}

	recordChan = make(chan *ReqRecord, BUFFER_SIZE) // 缓冲区大小为 100
}

func main() {
	go write2DB()
	r := gin.Default()
	r.NoRoute(routeProxy)   // NO Route is every Route!!!
	r.Run("127.0.0.1:8888")
}

// Then I can process all routes
func routeProxy(c *gin.Context) {
	req := c.Request
	go recordReq(req) // 记录日志
	if req.Method == http.MethodConnect {
		httpsProxy(c, req) // create http tunnel to process https
	} else {
		httpProxy(c, req) // process plain http
	}
}

func httpsProxy(c *gin.Context, req *http.Request) {
	// established connect tunnel
	address := req.URL.Host // it contains the port
	tunnelConn, err := net.DialTimeout("tcp", address, 10*time.Second)
	if err != nil {
		log.Println(err)
		return
	}
	log.Printf("try to established Connect Tunnel to: %s has been successfully.\n", address)
	tunnelrw := bufio.NewReadWriter(bufio.NewReader(tunnelConn), bufio.NewWriter(tunnelConn))
	c.Status(200) // 不加也没有问题,只是说 https 响应都会变成 404.
	// And We need to take over the http connection, Then make it become a TCP connection.
	hj, ok := c.Writer.(http.Hijacker)
	if !ok {
		http.Error(c.Writer, "webserver doesn't support hijacking", http.StatusInternalServerError)
		return
	}
	clientConn, bufrw, err := hj.Hijack()
	if err != nil {
		http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
		return
	}

	// 建立隧道之后,发送一个连接建立的响应(其实,只要状态码是 200 就可以了)
	if _, err = clientConn.Write([]byte(TUNNEL_PACKET)); err != nil {
		log.Printf("Response Failed: %v", err.Error())
	} else {
		log.Println("Response Success.")
	}

	// data flow direction: client <---> tunnel <---> server
	defer clientConn.Close()
	defer tunnelConn.Close()
	done := make(chan struct{})
	go transfer(bufrw, tunnelrw, done) // client --> proxy --> server
	go transfer(tunnelrw, bufrw, done) //server --> proxy --> client
	<-done
}

func httpProxy(c *gin.Context, req *http.Request) {
	req.RequestURI = "" // Must create a new Req or empty this.
	resp, err := proxyHttpClient.Do(req)
	if err != nil {
		log.Println(err)
		return
	}
	defer resp.Body.Close()
	c.Status(resp.StatusCode) // change the status code, default is 404 !!!
	for k, v := range resp.Header {
		c.Header(k, strings.Join(v, ",")) // write Header
	}
	c.Header("Server", RPOXY_SERVER) // haha, it just a kidding!!!
	io.Copy(c.Writer, resp.Body)     // and response data to client
}

// tunnel transfer data.
func transfer(from io.Reader, to io.Writer, ch chan<- struct{}) {
	io.Copy(to, from)
	ch <- struct{}{}
}

// Record http request
func recordReq(req *http.Request) {
	var schema = "HTTPS"
	if req.Method != http.MethodConnect {
		schema = "HTTP"
	}
	record := &ReqRecord{
		Schema: schema,
		Host:   req.Host,
		Port:   req.URL.Port(),
	}
	recordChan <- record
	log.Printf("Method: %s, Host: %s, URL: %s, Version: %s\n", req.Method, req.Host, req.URL.Path, req.Proto)
}

func write2DB() {
	log.Printf("Start to record request info...")
	records := make([]*ReqRecord, BUFFER_SIZE)
	idx := 0
	for {
		r := <-recordChan
		records[idx] = r
		idx++
		if idx == BUFFER_SIZE {
			// 批量写入
			DBConn.Create(&records)
			idx = 0 // reset position
			log.Printf("Write to DB successfully.")
		}
	}
}

二、演示

启动程序

在这里插入图片描述

配置系统代理

注:这里没有配置绕过环回地址这些,最后的方式是先启动 fiddler,然后把它的配置复制下来改一下就可以了。

在这里插入图片描述
查看 db 文件

昨天晚上到现在,记录了 2800 条数据了。因为这个代理的方式实现的类似早期的 HTTP 协议了,没有持久连接,可能是这个原因导致的请求更多了。

在这里插入图片描述

然后聚合查询看看访问最多的网站是什么,没想到居然是 dockerdesktop,这玩意开着就一直进行网络访问呀,不知道它干了什么!!!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/251157.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue3:直接修改reative的值,页面却不响应,这是什么情况?

目录 前言&#xff1a; 错误示范&#xff1a; reactive() 的局限性 解决办法&#xff1a; 1.使用ref 2.reative多套一层 3.使用Object.assign 前言&#xff1a; 今天看到有人在提问&#xff0c;问题是这样的&#xff0c;我修改了reative的值&#xff0c;数据居然失去了响…

详细了解stm32---按键

提示&#xff1a;永远支持知识文档免费开源&#xff0c;喜欢的朋友们&#xff0c;点个关注吧&#xff01;蟹蟹&#xff01; 目录 一、了解按键 二、stm32f103按键分析 三、按键应用 一、了解按键 同学们&#xff0c;又见面了o(*&#xffe3;▽&#xffe3;*)ブ&#xff0c;最…

【Java代码审计】XSS篇

【Java代码审计】XSS篇 1.Java中XSS常见触发位置2.反射型XSS3.存储型XSS4.XSS漏洞修复 1.Java中XSS常见触发位置 XSS漏洞产生后必然会有相关的输入/输出&#xff0c;因此我们只需快速找到这些输入/输出点&#xff0c;即可快速地进行跟踪发现漏洞。输入在Java中通常使用“reque…

ES6 面试题 | 02.精选 ES6 面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

C++试卷(华南理工大学)

华南理工大学期末考试 《高级语言程序设计&#xff08;I&#xff09;》A卷 注意事项&#xff1a; 1. 考前请将密封线内各项信息填写清楚&#xff1b; 2. 所有答案写在答题纸上&#xff0c;答在其它地方无效&#xff1b; 3&#xff0e;考试形式&#xff1a;闭卷&#xff1b…

LT7911D是TYPE-C/DP或者EDP转2 PORT MIPI和LVDS加音频

1.概述&#xff1a; T7911D是一款高性能TYPE-C/DP/EDP转2 PORT MIPI或者LVDS的芯片&#xff0c;目前主要在AR/VR或者显示器上应用的很多&#xff0c;对于DP1.2输入&#xff0c;LT7911D可配置为1/2/4车道。自适应均衡化使其适用于长电缆应用&#xff0c;最大带宽可达21.6Gbps。…

AI智能配音助手微信小程序前后端源码支持多种声音场景选择

大家好今天给大家带来一款配音小程序 &#xff0c;这款小程序支持多种不同声音和场景的选择更人性化&#xff0c; 比如说支持各地区的方言,英文,童声呀等等、 另外也支持男声女声的选择,反正就是模板那些非常的多 当然啦音量,语调,语速那些都是可以DIY跳转的哟,所以说这一款小程…

【单元测试】Junit 4--junit4 内置Rule

1.0 Rules ​ Rules允许非常灵活地添加或重新定义一个测试类中每个测试方法的行为。测试人员可以重复使用或扩展下面提供的Rules之一&#xff0c;或编写自己的Rules。 1.1 TestName ​ TestName Rule使当前的测试名称在测试方法中可用。用于在测试执行过程中获取测试方法名称…

​SQL (关系型) 数据库-fastapi集成

SQL (关系型) 数据库 - FastAPI FastAPI不需要你使用SQL(关系型)数据库。 但是您可以使用任何您想要的关系型数据库。 在这里&#xff0c;让我们看一个使用着SQLAlchemy的示例。 您可以很容易地将SQLAlchemy支持任何数据库&#xff0c;像&#xff1a; PostgreSQLMySQLSQLi…

云原生之深入解析Linkerd Service Mesh的功能和使用

一、简介 Linkerd 是 Kubernetes 的一个完全开源的服务网格实现&#xff0c;它通过为你提供运行时调试、可观测性、可靠性和安全性&#xff0c;使运行服务更轻松、更安全&#xff0c;所有这些都不需要对代码进行任何更改。Linkerd 通过在每个服务实例旁边安装一组超轻、透明的…

Python常见面试知识总结(一):迭代器、拷贝、线程及底层结构

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 今天来总结一下Python和C语言中常见的面试知识&#xff0c;欢迎大家一起前来探讨学习~ 【一】Python中迭代器的概念&#xff1f; 可迭代对象是迭代器、生成器和装饰器的基础。简单来说&#xff0c;可以使用for来循环遍历…

时序预测 | Python实现CNN电力需求预测

时序预测 | Python实现CNN电力需求预测 目录 时序预测 | Python实现CNN电力需求预测预测效果基本描述程序设计参考资料预测效果 基本描述 该数据集因其每小时的用电量数据以及 TSO 对消耗和定价的相应预测而值得注意,从而可以将预期预测与当前最先进的行业预测进行比较。使用该…

前端框架的虚拟DOM(Virtual DOM)

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

《PySpark大数据分析实战》-11.Spark on YARN模式安装Hadoop

&#x1f4cb; 博主简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是wux_labs。&#x1f61c; 热衷于各种主流技术&#xff0c;热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员&#xff08;PCTA&#xff09;、TiDB数据库专家&#xff08;PCTP…

Vue学习计划-Vue2--VueCLi(七)nextTick、、浏览器本地缓存、脚手架配置代理

1. nextTick 语法&#xff1a; this.$nextTick(回调函数)作用&#xff1a;在下一次DOM更新结束后执行其指定的回调什么时候用&#xff1a; 当改变数据后&#xff0c;要基于更新后的新DOM进行某些操作时&#xff0c;要在nextTick所指定的回调函数中执行 **举个栗子&#xff1a;…

B+树与索引

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 对于60%的程序员而言&a…

【 某景点舆情分析:Python、Echarts、Flask、文本处理技术的应用】

某景点舆情分析&#xff1a;Python、Echarts、Flask、文本处理技术的应用 前言技术栈数据获取与准备景点数据统计分析评论数据处理与分析词频统计分词与文本处理情感分析 数据可视化Web应用搭建结语 前言 随着旅游行业的蓬勃发展&#xff0c;越来越多的人通过网络平台获取关于…

SQL 入门指南:从零开始学习 SQL

当今时代&#xff0c;数据已经成为了我们生活中不可或缺的一部分。无论是企业的经营决策&#xff0c;还是个人的日常消费习惯&#xff0c;都需要通过对数据的收集、分析和应用来实现更好的结果。 而关系型数据库系统&#xff0c;作为最常见的数据存储和管理方式&#xff0c;SQ…

HNU-数据库系统-实验1-数据定义/数据操纵

数据库系统 课程实验1数据定义/数据操纵 计科210X 甘晴void 202108010XXX 目录 文章目录 数据库系统 课程实验1<br>数据定义/数据操纵实验目的实验样例实验环境实验内容1.1 数据库定义1&#xff09;实验内容与要求2&#xff09;实验重难点3&#xff09;实验基础知识①模…

设计模式之结构型设计模式(二):工厂模式 抽象工厂模式 建造者模式

工厂模式 Factory 1、什么是工厂模式 工厂模式旨在提供一种统一的接口来创建对象&#xff0c;而将具体的对象实例化的过程延迟到子类或者具体实现中。有助于降低客户端代码与被创建对象之间的耦合度&#xff0c;提高代码的灵活性和可维护性。 定义了一个创建对象的接口&…