diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ed3d2a1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + build: + name: Build on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-20.04, windows-2019] + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.20.10 + - name: Install dependencies + run: go get . + - name: Build + shell: bash + run: | + if [ "${{ matrix.os }}" = "ubuntu-20.04" ]; then + go build -trimpath -ldflags "-s -w" -o httpdump httpdump.go + elif [ "${{ matrix.os }}" = "windows-2019" ]; then + go build -trimpath -ldflags "-s -w" -o httpdump.exe httpdump.go + fi diff --git a/.github/workflows/release-setup.yml b/.github/workflows/release-setup.yml new file mode 100644 index 0000000..5ec3d62 --- /dev/null +++ b/.github/workflows/release-setup.yml @@ -0,0 +1,26 @@ +name: Release Setup + +on: + workflow_call + +jobs: + release: + name: "Continuous Release" + runs-on: ubuntu-latest + steps: + - name: Job info + run: | + echo "GitHub Ref: ${{ github.ref }}" + - name: Delete old workflow runs + uses: Mattraks/delete-workflow-runs@main + with: + retain_days: 2 + keep_minimum_runs: 2 + - name: Automatic release + uses: "marvinpinto/action-automatic-releases@latest" + if: startsWith(github.ref, 'refs/heads/') + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + automatic_release_tag: "continuous" + prerelease: true + title: "Continuous release" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4d215d0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,87 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Release + +on: + workflow_dispatch + +jobs: + setup: + name: Setup + uses: ./.github/workflows/release-setup.yml + build: + name: Build + runs-on: ${{ matrix.os }} + strategy: + matrix: + build: [linux, linux-aarch64, linux-armv7, linux-mipsle, windows] + include: + - build: linux + os: ubuntu-20.04 + archive-name: httpdump-linux-amd64.tar.gz + - build: linux-aarch64 + os: ubuntu-20.04 + archive-name: httpdump-linux-aarch64.tar.gz + - build: linux-armv7 + os: ubuntu-20.04 + archive-name: httpdump-linux-armv7.tar.gz + - build: linux-mipsle + os: ubuntu-20.04 + archive-name: httpdump-linux-mipsle.tar.gz + - build: windows + os: windows-2019 + archive-name: httpdump-windows10-amd64.7z + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.20.10 + - name: Install dependencies + run: go get . + - name: Build + shell: bash + run: | + if [ "${{ matrix.build }}" = "linux" ]; then + go build -trimpath -ldflags "-s -w" -o httpdump httpdump.go + elif [ "${{ matrix.build }}" = "linux-aarch64" ]; then + GOOS=linux GOARCH=arm64 go build -trimpath -ldflags "-s -w" -o httpdump httpdump.go + elif [ "${{ matrix.build }}" = "linux-armv7" ]; then + GOOS=linux GOARM=7 GOARCH=arm go build -trimpath -ldflags "-s -w" -o httpdump httpdump.go + elif [ "${{ matrix.build }}" = "linux-mipsle" ]; then + GOOS=linux GOARCH=mipsle go build -trimpath -ldflags "-s -w" -o httpdump httpdump.go + elif [ "${{ matrix.build }}" = "windows" ]; then + go build -trimpath -ldflags "-s -w" -o httpdump.exe httpdump.go + fi + - name: Build archive + shell: bash + run: | + mkdir archive + cp LICENSE README.md archive/ + # ls -lR + if [ "${{ matrix.build }}" = "windows" ]; then + cp httpdump.exe ./archive/ + cd archive + 7z a "${{ matrix.archive-name }}" LICENSE README.md httpdump.exe + else + cp httpdump ./archive/ + cd archive + tar -czf "${{ matrix.archive-name }}" LICENSE README.md httpdump + fi + - name: Continuous release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/heads/') + with: + prerelease: false + files: archive/${{ matrix.archive-name }} + tag_name: continuous + + - if: startsWith(github.ref, 'refs/tags/') + name: Tagged release + uses: softprops/action-gh-release@v1 + with: + files: archive/${{ matrix.archive-name }} + name: Release build (${{ github.ref_name }}) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5961b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.idea/* +!.idea/codeStyles +!.idea/*.iml +!.idea/modules.xml + +test.go +httpdump +httpdump.log diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..4a883ee --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/httpdump.iml b/.idea/httpdump.iml new file mode 100644 index 0000000..25ed3f6 --- /dev/null +++ b/.idea/httpdump.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..590354a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.run/go build httpdump.go.run.xml b/.run/go build httpdump.go.run.xml new file mode 100644 index 0000000..50e175f --- /dev/null +++ b/.run/go build httpdump.go.run.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..56fd1d9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 TLSLink + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab42906 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +## httpdump + +``` +Usage: ./httpdump [options] +Options: + -p string + http port (default "8080") +``` \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5fd4a51 --- /dev/null +++ b/go.mod @@ -0,0 +1,19 @@ +module httpdump + +go 1.21 + +require github.com/gofiber/fiber/v2 v2.50.0 + +require ( + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.50.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/sys v0.13.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7dfb28c --- /dev/null +++ b/go.sum @@ -0,0 +1,27 @@ +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/gofiber/fiber/v2 v2.50.0 h1:ia0JaB+uw3GpNSCR5nvC5dsaxXjRU5OEu36aytx+zGw= +github.com/gofiber/fiber/v2 v2.50.0/go.mod h1:21eytvay9Is7S6z+OgPi7c7n4++tnClWmhpimVHMimw= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M= +github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/httpdump.go b/httpdump.go new file mode 100644 index 0000000..5e3d282 --- /dev/null +++ b/httpdump.go @@ -0,0 +1,76 @@ +package main + +import ( + "flag" + "fmt" + "github.com/gofiber/fiber/v2" + "httpdump/pkg/log" + "os" + "os/signal" + "syscall" +) + +func main() { + port := flag.String("p", "8080", "http port") + respType := flag.String("t", "html", "response Content-Type: html, json, txt") + logPath := flag.String("l", "", "log path") + + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage: %s [options]\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "Options:\n") + flag.PrintDefaults() + } + flag.Parse() + + // init logger + log.Init(*logPath, "trace") + + app := fiber.New(fiber.Config{ + DisableStartupMessage: true, + DisableDefaultDate: true, + DisableHeaderNormalizing: true, + DisableDefaultContentType: true, + }) + // Match any request + app.Use(func(c *fiber.Ctx) error { + log.Trace(c.IP(), c.Request().String()) + err := c.Next() + // print response + // log.Debug(c.Response().String()) + return err + }) + app.Get("/", func(c *fiber.Ctx) error { + switch *respType { + case "json": + return c.JSON(fiber.Map{"hello": "world"}) + case "txt": + c.Set("Content-Type", "text/plain; charset=utf-8") + return c.SendString("hello world") + default: + c.Set("Content-Type", "text/html; charset=utf-8") + return c.SendString("hello world") + } + }) + // fmt.Println(*port) + go log.Fatal(app.Listen(fmt.Sprintf(":%s", *port))) + + watchSignal() +} + +func watchSignal() { + log.Info("Server pid:", os.Getpid()) + + sigs := make(chan os.Signal, 1) + // https://pkg.go.dev/os/signal + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) + for { + // 没有信号就阻塞,从而避免主协程退出 + sig := <-sigs + log.Info("Get signal:", sig) + switch sig { + default: + log.Info("Stop") + return + } + } +} diff --git a/pkg/log/log.go b/pkg/log/log.go new file mode 100644 index 0000000..da194da --- /dev/null +++ b/pkg/log/log.go @@ -0,0 +1,163 @@ +package log + +import ( + "fmt" + "log" + "os" + "path" + "strings" + "time" +) + +const ( + LevelTrace = iota + LevelDebug + LevelInfo + LevelWarn + LevelError + LevelFatal +) + +var ( + lw *logWriter + logger *log.Logger + logLevel int + levels map[int]string + + dateFormat = "2006-01-02" + logName = "httpdump.log" +) + +// 实现 os.Writer 接口 +type logWriter struct { + UseStdout bool + FileName string + File *os.File + NowDate string +} + +// 实现日志文件的切割 +func (lw *logWriter) Write(p []byte) (n int, err error) { + if !lw.UseStdout { + date := time.Now().Format(dateFormat) + if lw.NowDate != date { + _ = lw.File.Close() + _ = os.Rename(lw.FileName, lw.FileName+"."+lw.NowDate) + lw.NowDate = date + lw.newFile() + } + } + return lw.File.Write(p) +} + +// 创建新文件 +func (lw *logWriter) newFile() { + if lw.UseStdout { + lw.File = os.Stdout + return + } + + f, err := os.OpenFile(lw.FileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + panic(err) + } + lw.File = f +} + +func Init(logPath, level string) { + // 初始化 logger + lw = &logWriter{ + UseStdout: logPath == "", + FileName: path.Join(logPath, logName), + NowDate: time.Now().Format(dateFormat), + } + + lw.newFile() + logLevel = logLevel2Int(level) + logger = log.New(lw, "", log.LstdFlags|log.Lshortfile) +} + +func LoggerWriter() *logWriter { + return lw +} + +// Logger 获取 log.Logger +func Logger() *log.Logger { + return logger +} + +func Level() int { + return logLevel +} + +func logLevel2Int(l string) int { + levels = map[int]string{ + LevelTrace: "Trace", + LevelDebug: "Debug", + LevelInfo: "Info", + LevelWarn: "Warn", + LevelError: "Error", + LevelFatal: "Fatal", + } + lvl := LevelInfo + for k, v := range levels { + if strings.ToLower(l) == strings.ToLower(v) { + lvl = k + } + } + return lvl +} + +func output(l int, s ...interface{}) { + logger.SetPrefix(fmt.Sprintf("[%s] ", levels[l])) + _ = logger.Output(3, fmt.Sprintln(s...)) +} + +func Trace(v ...interface{}) { + l := LevelTrace + if logLevel > l { + return + } + output(l, v...) +} + +func Debug(v ...interface{}) { + l := LevelDebug + if logLevel > l { + return + } + output(l, v...) +} + +func Info(v ...interface{}) { + l := LevelInfo + if logLevel > l { + return + } + output(l, v...) +} + +func Warn(v ...interface{}) { + l := LevelWarn + if logLevel > l { + return + } + output(l, v...) +} + +func Error(v ...interface{}) { + l := LevelError + if logLevel > l { + return + } + output(l, v...) +} + +func Fatal(v ...interface{}) { + l := LevelFatal + if logLevel > l { + return + } + output(l, v...) + os.Exit(1) +}