Skip to content

Commit 6c071b9

Browse files
committed
initial commit
0 parents  commit 6c071b9

File tree

9 files changed

+276
-0
lines changed

9 files changed

+276
-0
lines changed

LICENSE

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2022 James Cunningham
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
SOFTWARE.

README.md

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# httplog
2+
3+
httplog is a command line tool that launches a local HTTP server that logs all requests it receives, replying with a canned response.
4+
5+
![screenshot of httplog](extra/screenshot.png)
6+
7+
## Install
8+
9+
Either download a [pre-built binary](https://github.com/jamescun/httplog/releases), or install from source (requires Go 1.19+):
10+
11+
```sh
12+
go install github.com/jamescun/httplog
13+
```
14+
15+
16+
## Usage
17+
18+
```
19+
httplog v1.0.0
20+
21+
httplog is a command line tool that launches a local HTTP server that logs all
22+
requests it receives, replying with a canned response.
23+
24+
Usage: httplog [options...]
25+
26+
Options:
27+
--help show helpful information
28+
--listen <host:port> configure the listening address for the HTTP
29+
server (default localhost:8080)
30+
--response <text> configure the canned body sent in response to
31+
all requests (default none)
32+
--response-code <code> configure the HTTP status code sent in response
33+
to all requests (default 200)
34+
--json log all requests as JSON rather than human
35+
readable text
36+
```
37+
38+
### Examples
39+
40+
Run with no options, a server will be launched on `localhost:8080`, answering no body and an HTTP 200 status code to all requests.
41+
42+
To configure the HTTP status code, use the `--response-code <code>` option, i.e. for a HTTP 500 Internal Server code:
43+
44+
```sh
45+
$ httplog --response-code 500
46+
47+
$ curl -v http://localhost:8080
48+
HTTP/1.1 500 Internal Server Error
49+
```
50+
51+
To configure the response body, use the `--response <text>` option, i.e. to response the text `hello world` to all requests:
52+
53+
```sh
54+
$ httplog --response "hello world"
55+
56+
$ curl http://localhost:8080
57+
Hello World
58+
```
59+
60+
If you would prefer JSON output, rather than human-readable text, use the `--json` option.

extra/screenshot.png

23.2 KB
Loading

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/jamescun/httplog
2+
3+
go 1.19

go.sum

Whitespace-only changes.

httplog/logger.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package httplog
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
"text/template"
7+
)
8+
9+
func JSONLogger(dst io.Writer, reqs <-chan *Request) {
10+
for req := range reqs {
11+
json.NewEncoder(dst).Encode(req)
12+
}
13+
}
14+
15+
var textBody = template.Must(template.New("").Parse(`
16+
{{ .At.Format "15:04:05.000" }}:
17+
Method: {{ .Method }} Path: {{ .Path }} Host: {{ .Host }} Proto: {{ .Proto }}
18+
{{- if .Query }}
19+
Query:
20+
{{- range $header, $values := .Query }}
21+
{{ $header }}: {{ range $i, $value := $values }}{{ if $i }},{{ end }}{{ $value }}{{ end -}}
22+
{{ end }}{{ end }}
23+
{{- if .Headers }}
24+
Headers:
25+
{{- range $header, $values := .Headers }}
26+
{{ $header }}: {{ range $i, $value := $values }}{{ if $i }},{{ end }}{{ $value }}{{ end -}}
27+
{{ end }}{{ end }}
28+
{{- if .Body }}
29+
Body:
30+
{{ .Body }}{{ end }}
31+
`))
32+
33+
func TextLogger(dst io.Writer, reqs <-chan *Request) {
34+
for req := range reqs {
35+
textBody.Execute(dst, req)
36+
}
37+
}

httplog/request.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package httplog
2+
3+
import (
4+
"time"
5+
)
6+
7+
type Request struct {
8+
Method string `json:"method"`
9+
Path string `json:"path"`
10+
Query map[string][]string `json:"query,omitempty"`
11+
Proto string `json:"proto"`
12+
Host string `json:"host"`
13+
Headers map[string][]string `json:"headers,omitempty"`
14+
Body string `json:"body,omitempty"`
15+
At time.Time `json:"at"`
16+
}

httplog/server.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package httplog
2+
3+
import (
4+
"encoding/base64"
5+
"io"
6+
"mime"
7+
"net/http"
8+
"time"
9+
)
10+
11+
type Server struct {
12+
// ResponseCode is the HTTP Status Code sent in response to all requests,
13+
// if not set, HTTP 200 is used.
14+
ResponseCode int
15+
16+
// ResponseBody is the contents sent in response to all requests, if not
17+
// set, no response body is used.
18+
ResponseBody []byte
19+
20+
requests chan *Request
21+
}
22+
23+
func NewServer(requestBufferSize int) *Server {
24+
return &Server{
25+
requests: make(chan *Request, requestBufferSize),
26+
}
27+
}
28+
29+
func (s *Server) Requests() <-chan *Request {
30+
return s.requests
31+
}
32+
33+
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
34+
req := &Request{
35+
Method: r.Method,
36+
Path: r.URL.Path,
37+
Proto: r.Proto,
38+
Headers: r.Header,
39+
Host: r.Host,
40+
At: time.Now(),
41+
}
42+
43+
q := r.URL.Query()
44+
if len(q) > 0 {
45+
req.Query = q
46+
}
47+
48+
body, _ := io.ReadAll(r.Body)
49+
if len(body) > 0 {
50+
contentType := r.Header.Get("Content-Type")
51+
mediaType, _, _ := mime.ParseMediaType(contentType)
52+
53+
switch mediaType {
54+
case "application/json", "application/x-www-form-urlencoded":
55+
req.Body = string(body)
56+
57+
default:
58+
req.Body = base64.StdEncoding.EncodeToString(body)
59+
}
60+
}
61+
62+
s.requests <- req
63+
64+
if s.ResponseCode > 0 {
65+
w.WriteHeader(s.ResponseCode)
66+
}
67+
68+
if len(s.ResponseBody) > 0 {
69+
w.Write(s.ResponseBody)
70+
}
71+
}

main.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"net/http"
7+
"os"
8+
9+
"github.com/jamescun/httplog/httplog"
10+
)
11+
12+
// Version is the semantic release version of this build of httplog.
13+
var Version = "0.0.0"
14+
15+
var (
16+
listenAddr = flag.String("listen", "localhost:8080", "configure the listening address for the HTTP server")
17+
responseBody = flag.String("response", "", "configure the canned body sent in response to all requests")
18+
responseCode = flag.Int("response-code", 200, "configure the HTTP status code sent in response requests")
19+
logJSON = flag.Bool("json", false, "log all requests as JSON rather than human readable text")
20+
)
21+
22+
const Usage = `httplog v%s
23+
24+
httplog is a command line tool that launches a local HTTP server that logs all
25+
requests it receives, replying with a canned response.
26+
27+
Usage: httplog [options...]
28+
29+
Options:
30+
--help show helpful information
31+
--listen <host:port> configure the listening address for the HTTP
32+
server (default localhost:8080)
33+
--response <text> configure the canned body sent in response to
34+
all requests (default none)
35+
--response-code <code> configure the HTTP status code sent in response
36+
to all requests (default 200)
37+
--json log all requests as JSON rather than human
38+
readable text
39+
`
40+
41+
func main() {
42+
flag.Usage = func() { fmt.Fprintf(os.Stderr, Usage, Version) }
43+
flag.Parse()
44+
45+
srv := httplog.NewServer(128)
46+
47+
srv.ResponseCode = *responseCode
48+
49+
if *responseBody != "" {
50+
srv.ResponseBody = []byte(*responseBody)
51+
}
52+
53+
if *logJSON {
54+
go httplog.JSONLogger(os.Stdout, srv.Requests())
55+
} else {
56+
go httplog.TextLogger(os.Stdout, srv.Requests())
57+
}
58+
59+
s := &http.Server{
60+
Addr: *listenAddr,
61+
Handler: srv,
62+
}
63+
64+
fmt.Fprintf(os.Stderr, "server listening on %s...\n", *listenAddr)
65+
66+
if err := s.ListenAndServe(); err != nil {
67+
fmt.Fprintln(os.Stderr, "server error:", err)
68+
os.Exit(1)
69+
}
70+
}

0 commit comments

Comments
 (0)