├── .gitignore ├── go.mod ├── README.md ├── go.sum ├── .github └── workflows │ └── go.yml ├── .goreleaser.yaml └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | gohttpd* 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ulricqin/gohttpd 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/codegangsta/negroni v1.0.0 7 | github.com/gorilla/mux v1.8.1 8 | ) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## gohttpd 功能 2 | 3 | - 类似 Python 的 SimpleHTTPServer,用于快速搭建一个 HTTP 服务 4 | - 支持文件上传 5 | - 支持远程执行命令 6 | - 提供了一个调试接口,请求这个接口会打印请求的 header 和 body,调试一些 webhook 的时候很有用 7 | 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY= 2 | github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= 3 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= 4 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 5 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | tags: ["v*"] 9 | 10 | jobs: 11 | 12 | gogogo: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Set up Go 19 | uses: actions/setup-go@v4 20 | with: 21 | go-version: '1.20' 22 | 23 | - name: Run GoReleaser 24 | uses: goreleaser/goreleaser-action@v3 25 | with: 26 | version: latest 27 | args: release --rm-dist 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | 5 | snapshot: 6 | name_template: '{{ .Tag }}' 7 | checksum: 8 | name_template: 'checksums.txt' 9 | 10 | builds: 11 | - id: build 12 | main: . 13 | binary: gohttpd 14 | env: 15 | - CGO_ENABLED=0 16 | goos: 17 | - linux 18 | goarch: 19 | - amd64 20 | - arm64 21 | ldflags: 22 | - -s -w 23 | 24 | archives: 25 | - id: gohttpd 26 | builds: 27 | - build 28 | format: tar.gz 29 | # format_overrides: 30 | # - goos: windows 31 | # format: zip 32 | name_template: "gohttpd-v{{ .Version }}-{{ .Os }}-{{ .Arch }}" 33 | wrap_in_directory: true 34 | files: 35 | - README.md 36 | 37 | release: 38 | github: 39 | owner: UlricQin 40 | name: gohttpd 41 | name_template: "v{{ .Version }}" 42 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net/http" 8 | "os" 9 | "os/exec" 10 | "runtime" 11 | "strconv" 12 | "strings" 13 | 14 | "github.com/codegangsta/negroni" 15 | "github.com/gorilla/mux" 16 | ) 17 | 18 | func init() { 19 | runtime.GOMAXPROCS(runtime.NumCPU()) 20 | log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) 21 | } 22 | 23 | func getwd() string { 24 | wd, err := os.Getwd() 25 | if err != nil { 26 | log.Fatalln("cannot getwd:", err) 27 | } 28 | return wd 29 | } 30 | 31 | func isExist(fp string) bool { 32 | _, err := os.Stat(fp) 33 | return err == nil || os.IsExist(err) 34 | } 35 | 36 | func getArgs() (int64, string) { 37 | args := os.Args 38 | l := len(args) 39 | 40 | portstr := "8888" 41 | authstr := "" 42 | if l >= 2 { 43 | // http port given 44 | portstr = args[1] 45 | } 46 | 47 | if l >= 3 { 48 | authstr = args[2] 49 | } 50 | 51 | port, err := strconv.ParseInt(portstr, 10, 64) 52 | if err != nil { 53 | log.Fatalln("cannot parse port:", portstr) 54 | } 55 | 56 | return port, authstr 57 | } 58 | 59 | func main() { 60 | port, auth := getArgs() 61 | startHttp(port, auth) 62 | } 63 | 64 | func startHttp(port int64, authstr string) { 65 | r := mux.NewRouter().StrictSlash(false) 66 | 67 | r.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) { 68 | fmt.Fprintf(w, "pong") 69 | }) 70 | 71 | r.HandleFunc("/print", func(w http.ResponseWriter, r *http.Request) { 72 | fmt.Printf("r.RequestURI: %s\n", r.RequestURI) 73 | fmt.Printf("r.URL.Path: %s\n", r.URL.Path) 74 | fmt.Printf("r.URL.Host: %s\n", r.URL.Host) 75 | fmt.Printf("r.URL.Hostname(): %s\n", r.URL.Hostname()) 76 | fmt.Printf("r.Method: %s\n", r.Method) 77 | fmt.Printf("r.URL.Scheme: %s\n", r.URL.Scheme) 78 | fmt.Printf("gohttpd pid: %d\n", os.Getpid()) 79 | 80 | fmt.Printf("\nHeaders: \n") 81 | for name, values := range r.Header { 82 | if len(values) == 1 { 83 | fmt.Printf("%s: %v\n", name, values[0]) 84 | continue 85 | } 86 | 87 | fmt.Println(name) 88 | for i := 0; i < len(values); i++ { 89 | fmt.Printf(" - #%d: %s\n", i, values[i]) 90 | } 91 | } 92 | 93 | fmt.Printf("\nPayload: \n") 94 | defer r.Body.Close() 95 | bs, _ := io.ReadAll(r.Body) 96 | fmt.Println(string(bs)) 97 | fmt.Fprintln(w, "ok") 98 | }) 99 | 100 | r.HandleFunc("/request", func(w http.ResponseWriter, r *http.Request) { 101 | fmt.Fprintf(w, "r.RequestURI: %s\n", r.RequestURI) 102 | fmt.Fprintf(w, "r.URL.Path: %s\n", r.URL.Path) 103 | fmt.Fprintf(w, "r.URL.Host: %s\n", r.URL.Host) 104 | fmt.Fprintf(w, "r.URL.Hostname(): %s\n", r.URL.Hostname()) 105 | fmt.Fprintf(w, "r.Method: %s\n", r.Method) 106 | fmt.Fprintf(w, "r.URL.Scheme: %s\n", r.URL.Scheme) 107 | fmt.Fprintf(w, "gohttpd pid: %d\n", os.Getpid()) 108 | 109 | fmt.Fprintf(w, "\nHeaders: \n") 110 | for name, values := range r.Header { 111 | if len(values) == 1 { 112 | fmt.Fprintf(w, "%s: %v\n", name, values[0]) 113 | continue 114 | } 115 | 116 | fmt.Fprintln(w, name) 117 | for i := 0; i < len(values); i++ { 118 | fmt.Fprintf(w, " - #%d: %s\n", i, values[i]) 119 | } 120 | } 121 | 122 | fmt.Fprintf(w, "\nPayload: \n") 123 | defer r.Body.Close() 124 | bs, _ := io.ReadAll(r.Body) 125 | fmt.Fprintln(w, string(bs)) 126 | }) 127 | 128 | r.HandleFunc("/run", func(w http.ResponseWriter, r *http.Request) { 129 | if authstr == "" { 130 | http.Error(w, "not supported. use ./httpd to enable", 200) 131 | return 132 | } 133 | 134 | if r.Body == nil { 135 | http.Error(w, "body is nil", http.StatusBadRequest) 136 | return 137 | } 138 | 139 | auth := r.Header.Get("Authorization") 140 | if strings.TrimSpace(auth) == "" { 141 | http.Error(w, "Authorization is blank", http.StatusBadRequest) 142 | return 143 | } 144 | 145 | auth = strings.TrimPrefix(auth, "Bearer ") 146 | if auth != authstr { 147 | http.Error(w, "Authorization invalid", http.StatusForbidden) 148 | return 149 | } 150 | 151 | defer r.Body.Close() 152 | bs, _ := io.ReadAll(r.Body) 153 | sh := string(bs) 154 | 155 | cmd := exec.Command("sh", "-c", sh) 156 | output, err := cmd.CombinedOutput() 157 | if err != nil { 158 | http.Error(w, fmt.Sprintf("exec `%s` fail: %v", sh, err), 200) 159 | return 160 | } 161 | 162 | w.Write(output) 163 | }) 164 | 165 | r.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) { 166 | if r.Method == "GET" { 167 | w.Write([]byte(UPLOAD_HTML)) 168 | return 169 | } 170 | 171 | r.ParseMultipartForm(32 << 20) 172 | files := r.MultipartForm.File["files"] 173 | len := len(files) 174 | for i := 0; i < len; i++ { 175 | file, err := files[i].Open() 176 | if err != nil { 177 | http.Error(w, err.Error(), 200) 178 | return 179 | } 180 | 181 | defer file.Close() 182 | 183 | filepath := "./" + files[i].Filename 184 | if isExist(filepath) { 185 | err = os.Remove(filepath) 186 | if err != nil { 187 | http.Error(w, err.Error(), 200) 188 | return 189 | } 190 | } 191 | 192 | cur, err := os.Create(filepath) 193 | if err != nil { 194 | http.Error(w, err.Error(), 200) 195 | return 196 | } 197 | 198 | defer cur.Close() 199 | 200 | io.Copy(cur, file) 201 | } 202 | 203 | w.Write([]byte(UPLOAD_RESULT_HTML)) 204 | }) 205 | 206 | r.PathPrefix("/").Handler(http.FileServer(http.Dir("./"))) 207 | 208 | n := negroni.New() 209 | n.UseHandler(r) 210 | 211 | log.Println("listening http on", port) 212 | log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), n)) 213 | } 214 | 215 | var UPLOAD_HTML = ` 216 | 217 | 218 | 219 | 220 | gohttpd 221 | 262 | 263 | 264 | 265 |
266 | Upload files: 267 | 268 | 269 |
270 | 271 | 272 | 273 | ` 274 | 275 | var UPLOAD_RESULT_HTML = ` 276 | 277 | 278 | 279 | 280 | gohttpd 281 | 287 | 288 | 289 | 290 |

upload successfully | go home

291 | 292 | 293 | 294 | ` 295 | --------------------------------------------------------------------------------