diff --git a/.editorconfig b/.editorconfig index c6e540b..654c1e0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,3 +13,6 @@ indent_style = tab [*.md] trim_trailing_whitespace = false eclint_indent_style = unset + +[*.yml,*.yaml] +indent_size = 2 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a0ad87d..97afd01 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,34 +1,34 @@ name: Docker Image CI on: - push: - tags: ["v*.*.*"] + push: + tags: ["v*.*.*"] env: - DOCKER_USER: marcelstolin - IMAGE_NAME: wildcardhttp + DOCKER_USER: marcelstolin + IMAGE_NAME: wildcardhttp jobs: - build: - runs-on: ubuntu-latest + build: + runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 + steps: + - name: Checkout repository + uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Log into Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ env.DOCKER_USER }} - password: ${{ secrets.DOCKER_TOKEN }} + - name: Log into Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ env.DOCKER_USER }} + password: ${{ secrets.DOCKER_TOKEN }} - - name: Build and push Docker image - id: build-and-push - uses: docker/build-push-action@v6 - with: - context: . - push: ${{ github.event_name != 'pull_request' }} - tags: "${{ env.DOCKER_USER }}/${{ env.IMAGE_NAME }}:latest,${{ env.DOCKER_USER }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}" + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: "${{ env.DOCKER_USER }}/${{ env.IMAGE_NAME }}:latest,${{ env.DOCKER_USER }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}" diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index c908e28..8ba8efc 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,29 +1,43 @@ -name: Go - +name: Go CI on: - push: - branches: ["main"] - pull_request: - branches: ["main"] + push: + branches: + - main + pull_request: + branches: + - main paths-ignore: - - "**/Readme.md" - - ".editorconfig" + - Readme.md + - .editorconfig +permissions: + contents: read + pull-requests: read + checks: write jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: "1.23.4" - - - name: Build - run: make build - - - uses: actions/upload-artifact@v4 - with: - name: whttp - path: whttp + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 1.23.4 + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.60 + build: + needs: golangci + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 1.23.4 + - name: Build + run: make build + - uses: actions/upload-artifact@v4 + with: + name: whttp + path: whttp diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 0000000..27851cf --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,8 @@ +semi: false +tabWidth: 4 +overrides: + - files: + - "**/*.yml" + - "**/*.yaml" + options: + tabWidth: 2 diff --git a/main.go b/main.go index 70c9177..76d15ad 100644 --- a/main.go +++ b/main.go @@ -1,41 +1,107 @@ package main import ( + "encoding/json" "fmt" + "io" "log" "net/http" - "net/http/httputil" "os" + "strconv" + "strings" ) -const HelpText = `Usage: whttp [] +const HelpText = `Usage: whttp [] -Provide the port number as part of the host or as a single argument. -For example: localhost:5000, :5000, or localhost 5000.` +host : The TCP network host to accept connections on; e.g. localhost:5000 or :5000 +JSON : Serve any response in JSON format; Default: false + +For example: wget :5000 true` + +const PrettyHeaderFormat = "Header\n======\n%s" +const PrettyBodyFormat = "Body\n====\n%s" + +type RequestData struct { + Header map[string][]string + Body string +} + +func (data RequestData) prettyPrintHeader() string { + var output string + for key, values := range data.Header { + output += fmt.Sprintf("%s: %s\n", key, strings.Join(values, ", ")) + } + return output +} + +func (data RequestData) PrettyPrint() string { + prettyHeader := fmt.Sprintf(PrettyHeaderFormat, data.prettyPrintHeader()) + var prettyBody string + if len(data.Body) > 0 { + prettyBody = fmt.Sprintf(PrettyBodyFormat, data.Body) + } + return fmt.Sprintf("%s\n%s", prettyHeader, prettyBody) +} + +func NewRequestData(r *http.Request) (RequestData, error) { + var data RequestData + data.Header = r.Header + if body, err := io.ReadAll(r.Body); err == nil { + data.Body = string(body) + } else { + return data, err + } + return data, nil +} // This function simply prints a dump of the http request. func handleAny(w http.ResponseWriter, r *http.Request) { - dump, err := httputil.DumpRequest(r, true) - if err != nil { + if requestData, err := NewRequestData(r); err == nil { + fmt.Fprintf(w, "%s", requestData.PrettyPrint()) + } else { + w.WriteHeader(500) + fmt.Fprintf(w, "%e", err) + return + } +} + +func handleAnyJson(w http.ResponseWriter, r *http.Request) { + if requestData, err := NewRequestData(r); err == nil { + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(requestData); err != nil { + w.WriteHeader(500) + fmt.Fprintf(w, "%e", err) + return + } + } else { w.WriteHeader(500) fmt.Fprintf(w, "%e", err) return } - fmt.Fprintf(w, "%s", dump) } func main() { var addr string + var serveJson bool if len(os.Args[1:]) == 1 { addr = os.Args[1] } else if len(os.Args[1:]) == 2 { - addr = fmt.Sprintf("%s:%s", os.Args[1], os.Args[2]) + addr = os.Args[1] + if _serveJson, err := strconv.ParseBool(os.Args[2]); err == nil { + serveJson = _serveJson + } else { + log.Fatal(err) + } } else { - log.Panic(HelpText) - return + log.Fatal(HelpText) } - handler := http.HandlerFunc(handleAny) + var handler http.Handler + if serveJson { + handler = http.HandlerFunc(handleAnyJson) + } else { + handler = http.HandlerFunc(handleAny) + } log.Fatal(http.ListenAndServe(addr, handler)) }