Skip to content

Commit fd955af

Browse files
committed
Move http from server package to protocol package
Protocol package handles the different types of incoming requests rq accepts. Currently http is the only protocol but this allows other protocols to use the interface. Ie: Websockets - Created a payload struct which we use between channels. - Moved the errorLog logger interface to this package because no other package needs to reuse it. - Changes to the start method: - Added a wg which allows us to control the goroutine from the server package. - Added a slice of renderer channels which we send incoming requests to. - Added a slice of quit channels to close renderers in the case that the http protocol has a fatal error.
1 parent 971f4cd commit fd955af

File tree

4 files changed

+253
-163
lines changed

4 files changed

+253
-163
lines changed
Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,67 @@
1-
package server
1+
package protocol
22

33
import (
44
"fmt"
55
"log"
66
"net/http"
7+
"sync"
78

89
"github.com/aaronvb/logparams"
910
"github.com/aaronvb/logrequest"
10-
"github.com/aaronvb/request_hole/pkg/renderer"
1111
"github.com/gorilla/mux"
1212
"github.com/pterm/pterm"
1313
)
1414

15+
// Http is the protocol for accepting http requests.
1516
type Http struct {
16-
// Port is the port the HTTP server will run on.
17-
Port int
18-
1917
// Addr is the address the HTTP server will bind to.
2018
Addr string
2119

20+
// Port is the port the HTTP server will run on.
21+
Port int
22+
2223
// ResponseCode is the response which out endpoint will return.
2324
// Default is 200 if no response code is passed.
2425
ResponseCode int
2526

26-
// Output is the Renderer interface.
27-
Output renderer.Renderer
28-
29-
// LogOutput
30-
LogOutput renderer.Renderer
31-
32-
// Determines if header details should be shown with the request
33-
Details bool
27+
// rendererChannel is the channel which we send a RequestPayload to when
28+
// receiving an incoming request to the Http protocol.
29+
rendererChannels []chan RequestPayload
3430
}
3531

3632
// Start will start the HTTP server.
37-
func (s *Http) Start() {
38-
s.Output.Start()
39-
s.LogOutput.Start()
40-
33+
//
34+
// Sets the channel on our struct so that incoming requests can be sent over it.
35+
//
36+
// In the case that we cannot start this server, we send a signal to our quit channel
37+
// to close renderers.
38+
func (s *Http) Start(wg *sync.WaitGroup, c []chan RequestPayload, quits []chan int) {
4139
addr := fmt.Sprintf("%s:%d", s.Addr, s.Port)
42-
errorLog := log.New(&renderer.PrinterLog{Prefix: pterm.Error}, "", 0)
40+
errorLog := log.New(&httpErrorLog{}, "", 0)
4341

4442
srv := &http.Server{
4543
Addr: addr,
4644
ErrorLog: errorLog,
4745
Handler: s.routes(),
4846
}
4947

48+
s.rendererChannels = c
49+
50+
defer wg.Done()
51+
5052
err := srv.ListenAndServe()
51-
s.Output.Fatal(err)
53+
str := pterm.Error.WithShowLineNumber(false).Sprintf("%s\n", err)
54+
pterm.Printo(str) // Overwrite last line
55+
56+
for _, quit := range quits {
57+
quit <- 1
58+
}
5259
}
5360

5461
// routes handles the routes for our HTTP server and currently accepts any path.
5562
func (s *Http) routes() http.Handler {
5663
r := mux.NewRouter()
5764
r.PathPrefix("/").HandlerFunc(s.defaultHandler)
58-
5965
r.Use(s.logRequest)
6066

6167
return r
@@ -75,12 +81,24 @@ func (s *Http) logRequest(next http.Handler) http.Handler {
7581
fields := lr.ToFields()
7682
params := logparams.LogParams{Request: r, HidePrefix: true}
7783

78-
s.Output.IncomingRequest(fields, params.ToString())
79-
s.LogOutput.IncomingRequest(fields, params.ToString())
84+
req := RequestPayload{
85+
Fields: fields,
86+
Params: params.ToString(),
87+
Headers: r.Header,
88+
}
8089

81-
if s.Details {
82-
s.Output.IncomingRequestHeaders(r.Header)
83-
s.LogOutput.IncomingRequestHeaders(r.Header)
90+
for _, rendererChannel := range s.rendererChannels {
91+
rendererChannel <- req
8492
}
8593
})
8694
}
95+
96+
// httpErrorLog implements the logger interface.
97+
type httpErrorLog struct{}
98+
99+
// Write let's us override the logger required for http errors and
100+
// prints to the terminal using pterm.
101+
func (e *httpErrorLog) Write(b []byte) (n int, err error) {
102+
pterm.Error.WithShowLineNumber(false).Println(string(b))
103+
return len(b), nil
104+
}

pkg/protocol/http_test.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package protocol
2+
3+
import (
4+
"bytes"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
)
9+
10+
func TestResponseCodeFlag(t *testing.T) {
11+
tests := []int{200, 404, 201, 500}
12+
13+
for _, respCode := range tests {
14+
httpServer := Http{ResponseCode: respCode}
15+
srv := httptest.NewServer(httpServer.routes())
16+
req, err := http.NewRequest(http.MethodGet, srv.URL+"/", nil)
17+
if err != nil {
18+
t.Error(err)
19+
}
20+
21+
resp, err := http.DefaultClient.Do(req)
22+
if err != nil {
23+
t.Error(err)
24+
}
25+
26+
if resp.StatusCode != respCode {
27+
t.Errorf("Expected %d, got %d", respCode, resp.StatusCode)
28+
}
29+
resp.Body.Close()
30+
srv.Close()
31+
}
32+
}
33+
34+
func TestLogRequestOneRenderer(t *testing.T) {
35+
testTable := []struct {
36+
method string
37+
path string
38+
body string
39+
expectedParams string
40+
headerKey string
41+
headerValue string
42+
}{
43+
{
44+
http.MethodGet,
45+
"/foo",
46+
"",
47+
"",
48+
"Foo",
49+
"Bar",
50+
},
51+
{
52+
http.MethodPost,
53+
"/foo/bar",
54+
"{\"foo\": \"bar\"}",
55+
"{\"foo\" => \"bar\"}",
56+
"Content-Type",
57+
"application/json",
58+
},
59+
{
60+
http.MethodDelete,
61+
"/foo/1",
62+
"",
63+
"",
64+
"Bearer",
65+
"hello!",
66+
},
67+
{
68+
http.MethodGet,
69+
"/foo/bar?hello=world",
70+
"",
71+
"{\"hello\" => \"world\"}",
72+
"Content-Type",
73+
"application/json!",
74+
},
75+
}
76+
77+
rpChannel := make(chan RequestPayload, len(testTable))
78+
httpServer := Http{ResponseCode: 200, rendererChannels: []chan RequestPayload{rpChannel}}
79+
srv := httptest.NewServer(httpServer.routes())
80+
defer srv.Close()
81+
82+
for _, test := range testTable {
83+
b := bytes.NewBuffer([]byte(test.body))
84+
req, err := http.NewRequest(test.method, srv.URL+test.path, b)
85+
86+
if test.headerKey != "" {
87+
req.Header.Set(test.headerKey, test.headerValue)
88+
}
89+
90+
if err != nil {
91+
t.Error(err)
92+
}
93+
94+
resp, err := http.DefaultClient.Do(req)
95+
if err != nil {
96+
t.Error(err)
97+
}
98+
resp.Body.Close()
99+
100+
rp := <-rpChannel
101+
102+
if rp.Fields.Method != test.method {
103+
t.Errorf("Expected %s, got %s", test.method, rp.Fields.Method)
104+
}
105+
106+
if rp.Fields.Url != test.path {
107+
t.Errorf("Expected %s, got %s", test.path, rp.Fields.Url)
108+
}
109+
110+
if rp.Params != test.expectedParams {
111+
t.Errorf("Expected %s, got %s", test.expectedParams, rp.Params)
112+
}
113+
114+
expectedHeaderValue := rp.Headers[test.headerKey][0]
115+
if expectedHeaderValue != test.headerValue {
116+
t.Errorf("Expected %s, got %s", expectedHeaderValue, test.headerValue)
117+
}
118+
}
119+
}
120+
121+
func TestLogRequestManyRenderers(t *testing.T) {
122+
testTable := []struct {
123+
method string
124+
path string
125+
Body string
126+
expectedParams string
127+
}{
128+
{http.MethodGet, "/foo", "", ""},
129+
{http.MethodPost, "/foo/bar", "{\"foo\": \"bar\"}", "{\"foo\" => \"bar\"}"},
130+
{http.MethodDelete, "/foo/1", "", ""},
131+
{http.MethodGet, "/foo/bar?hello=world", "", "{\"hello\" => \"world\"}"},
132+
}
133+
134+
rpChannelA := make(chan RequestPayload, len(testTable))
135+
rpChannelB := make(chan RequestPayload, len(testTable))
136+
httpServer := Http{
137+
ResponseCode: 200,
138+
rendererChannels: []chan RequestPayload{rpChannelA, rpChannelB}}
139+
srv := httptest.NewServer(httpServer.routes())
140+
defer srv.Close()
141+
142+
for _, test := range testTable {
143+
b := bytes.NewBuffer([]byte(test.Body))
144+
req, err := http.NewRequest(test.method, srv.URL+test.path, b)
145+
146+
if test.Body != "" {
147+
req.Header.Set("Content-Type", "application/json")
148+
}
149+
150+
if err != nil {
151+
t.Error(err)
152+
}
153+
154+
resp, err := http.DefaultClient.Do(req)
155+
if err != nil {
156+
t.Error(err)
157+
}
158+
resp.Body.Close()
159+
160+
rpA := <-rpChannelA
161+
rpB := <-rpChannelB
162+
163+
if rpA.Fields.Method != test.method {
164+
t.Errorf("Expected %s, got %s", test.method, rpA.Fields.Method)
165+
}
166+
167+
if rpA.Fields.Url != test.path {
168+
t.Errorf("Expected %s, got %s", test.path, rpA.Fields.Url)
169+
}
170+
171+
if rpA.Params != test.expectedParams {
172+
t.Errorf("Expected %s, got %s", test.expectedParams, rpA.Params)
173+
}
174+
175+
if rpB.Fields.Method != test.method {
176+
t.Errorf("Expected %s, got %s", test.method, rpB.Fields.Method)
177+
}
178+
179+
if rpB.Fields.Url != test.path {
180+
t.Errorf("Expected %s, got %s", test.path, rpB.Fields.Url)
181+
}
182+
183+
if rpB.Params != test.expectedParams {
184+
t.Errorf("Expected %s, got %s", test.expectedParams, rpB.Params)
185+
}
186+
}
187+
}

pkg/protocol/protocol.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package protocol
2+
3+
import (
4+
"sync"
5+
6+
"github.com/aaronvb/logrequest"
7+
)
8+
9+
// Protocol is the interface for the servers that accept incoming requests.
10+
// Incoming requests are then sent to the renderers through the RequestPayload channel.
11+
// If a protocol closes(ie: from and error), we use the second channel which is used to
12+
// send an int(1 signals quit).
13+
type Protocol interface {
14+
Start(*sync.WaitGroup, []chan RequestPayload, []chan int)
15+
}
16+
17+
// RequestPayload is the request payload we receive from an incoming request that we use with
18+
// the renderers.
19+
type RequestPayload struct {
20+
Fields logrequest.RequestFields `json:"fields"`
21+
Headers map[string][]string `json:"headers"`
22+
Params string `json:"params"`
23+
}

0 commit comments

Comments
 (0)