plaprox

Proxy for plausible.io scripts written in Go.
git clone git://vcs.sapka.me/plaprox
Log | Files | Refs | README | LICENSE

commit 91156ec9a54651b4c1eef7020e3b4cd71e3e23e7
parent 52a2ba0fdeb3677dd9675afce2b8be84d8a31d48
Author: Michał M. Sapka <michal@sapka.me>
Date:   Sat, 15 Jul 2023 22:46:46 +0200

feat: proxy calls to Plausible

and fix tests
and fix readme

Diffstat:
MREADME | 24+++++++++++++++++++++---
Mmain.go | 73+++++++++++++++++++++++++++++++++++++------------------------------------
Mmain_test.go | 54+++++++++++++++++++++++++++---------------------------
3 files changed, 85 insertions(+), 66 deletions(-)

diff --git a/README b/README @@ -21,6 +21,10 @@ OpenBSD/Httpd/Relayd. I love this setup, but it does not provide proxy_pass. So I wrote this little fella. +License +------- +Refer to LICENSE + Architecture ------------ @@ -41,10 +45,19 @@ Contributing Found this interesting? Cool! Know how to improve things? Even better! Email me a git patch - my email can be found at https://michal.sapka.me/about/. +Telling me how to do Go properly is also an option. -Known problems --------------- + +Known problems and ideas +----------------------- - GET requests can be cached. Plaprox does not currently do this. +- Script will break when handling gzip responses. For now we remove headers, so + Plausible will not compress response. It would be cool to work with gzip and + return gzip if requestes. +- An option to force not running as a deamon would be nice. Currently one needs + to uncomment two lines in code. +- An option to set port. Currently one needs to modify the code. +- An option to define location of logs. Testing @@ -53,6 +66,7 @@ You can run the tests via: go test +Please note that I run this on my server running OpenBSD. No other OS is tested. Compiling --------- @@ -71,7 +85,7 @@ You can run the app as a process: ./plaprox -But the preferred way is to treat it a demon. Please refer to documentation of +But the preferred way is to treat it a daemon. Please refer to documentation of your OS of choice @@ -82,3 +96,7 @@ serveProxy() in main.go. Add another handleFunc for the required path, and any requests to the proxy will be handled automatically: http.HandleFunc("/another/proxied/path.js", Proxy) + +Donate +----- +Want to donate? Great! Try to find a good cause, like OpenBSD fundation. diff --git a/main.go b/main.go @@ -1,20 +1,5 @@ //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || plan9p -/* - Copyright 2023 Michał Sapka (https://michal.sapka.me) - - Permission to use, copy, modify, and/or distribute this software for - any purpose with or without fee is hereby granted. - - THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL - WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE - FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY - DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT - OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - package main import ( @@ -41,8 +26,9 @@ func init() { func main() { - serveProxy() - return + // Remove comments to act as standard process + serveProxy() + return cntxt := &daemon.Context{ PidFileName: "plaprox.pid", @@ -66,24 +52,47 @@ func main() { serveProxy() } -// Proxies requests to Plausible.io and returns the response to the client +func serveProxy() { + // Add more endpoints if needed + http.HandleFunc("/js/script.js", Proxy) + http.HandleFunc("/api/event", Proxy) + + if err := http.ListenAndServe(":9090", nil); err != nil { + log.Fatal(err) + } +} + func Proxy(w http.ResponseWriter, r *http.Request) { - req, _ := http.NewRequest(r.Method, "https://plausible.io"+r.URL.Path, r.Body) + log.Print("trying to proxy " + r.URL.Path) + req, err := http.NewRequest(r.Method, "https://plausible.io"+r.URL.Path, r.Body) + if err != nil { + log.Fatal(err) + return + } - /* - Forwarding headers breaks reading the returned file in browsers. Seems to be - not needed, so I'll just comment this logic out - for key, values := range r.Header { + for key, values := range r.Header { + // We don't want to deal with gzip + if key != "Accept-Encoding" { req.Header.Del(key) for _, value := range values { req.Header.Add(key, value) } } - */ - resp, _ := Client.Do(req) + } + + resp, err := Client.Do(req) + if err != nil { + log.Fatal(err) + return + } + defer resp.Body.Close() - bodyBytes, _ := io.ReadAll(resp.Body) + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + return + } bodyString := string(bodyBytes) for key, values := range resp.Header { @@ -93,15 +102,7 @@ func Proxy(w http.ResponseWriter, r *http.Request) { } } - fmt.Fprintf(w, bodyString) -} + log.Print("ok " + r.URL.Path) -func serveProxy() { - - http.HandleFunc("/js/script.js", Proxy) - http.HandleFunc("/api/event", Proxy) - - if err := http.ListenAndServe(":9090", nil); err != nil { - log.Fatal(err) - } + fmt.Fprintf(w, bodyString) } diff --git a/main_test.go b/main_test.go @@ -2,13 +2,10 @@ package main import ( "bytes" - "fmt" "io/ioutil" "net/http" "net/http/httptest" - // "net/http/httputil" - "testing" ) @@ -21,8 +18,7 @@ func (m *MockClient) Do(req *http.Request) (*http.Response, error) { return m.MockDo(req) } -func mockProxiedCall(method string, url string, responseBody string, sentBody string, headers map[string][]string, t *testing.T) { - +func mockCall(method string, url string, responseBody string, sentBody string, sentHeaders map[string][]string, t *testing.T) { r := ioutil.NopCloser(bytes.NewReader([]byte(responseBody))) Client = &MockClient{ MockDo: func(req *http.Request) (*http.Response, error) { @@ -37,27 +33,23 @@ func mockProxiedCall(method string, url string, responseBody string, sentBody st t.Errorf("Invalid method. Got: %q, want: %q", req.Method, method) } - // if req.Method == "POST" { - // req.ParseForm() - // responseBody2 := req.Form - - // fmt.Println("inm test", req.Body) + if req.Method == "POST" { + reqBody, _ := ioutil.ReadAll(req.Body) + stringReqBody := string(reqBody) + if stringReqBody != string(sentBody) { + t.Errorf("Invalid body sent to Plausible. Got: %q, want: %q", sentBody, stringReqBody) - // jo, _ := httputil.DumpRequest(req, true) - // fmt.Println("fuck", string(jo[:])) - - // } - - /* - if fmt.Sprint(req.Header) != fmt.Sprint(headers) { - t.Errorf("Invalid proxied headers. Got: %q, want: %q", req.Header, headers) } - */ + } + + if fmt.Sprint(req.Header) != fmt.Sprint(sentHeaders) { + t.Errorf("Invalid sent headers. Got: %q, want: %q", req.Header, sentHeaders) + } return &http.Response{ StatusCode: 200, Body: r, - Header: headers, + Header: sentHeaders, }, nil }, @@ -67,7 +59,7 @@ func mockProxiedCall(method string, url string, responseBody string, sentBody st func TestProxy(t *testing.T) { t.Run("GET request", func(t *testing.T) { - proxiedBody := `[{"full_name": "mock-repo" }]` + responseBody := `[{"full_name": "mock-repo" }]` sentBody := "{}" wantedHeaders := map[string][]string{ "Key": []string{"value"}, @@ -75,7 +67,7 @@ func TestProxy(t *testing.T) { "Content-Type": []string{"application/javascript"}, } - mockProxiedCall(http.MethodGet, "/js/script.js", proxiedBody, sentBody, wantedHeaders, t) + mockCall(http.MethodGet, "/js/script.js", responseBody, sentBody, wantedHeaders, t) request, _ := http.NewRequest(http.MethodGet, "/js/script.js", nil) @@ -83,13 +75,15 @@ func TestProxy(t *testing.T) { request.Header.Add("key2", "value1") request.Header.Add("key2", "value2") request.Header.Set("Content-Type", "application/javascript") + // we remove Accept-Encoding header not to deal with gzip + request.Header.Set("Accept-Encoding", "gzip") response := httptest.NewRecorder() Proxy(response, request) - if response.Body.String() != proxiedBody { - t.Errorf("got %q, want %q", response.Body.String(), proxiedBody) + if response.Body.String() != responseBody { + t.Errorf("Invalid returned body. got %q, want %q", response.Body.String(), responseBody) } if fmt.Sprint(response.Header()) != fmt.Sprint(wantedHeaders) { @@ -98,11 +92,13 @@ func TestProxy(t *testing.T) { }) t.Run("POST request", func(t *testing.T) { - proxiedBody := "{}" + responseBody := "ok" sentBody := `{"full_name": "mock-repo" }` - wantedHeaders := map[string][]string{} + wantedHeaders := map[string][]string{ + "Content-Type": []string{"application/javascript"}, + } - mockProxiedCall(http.MethodPost, "/js/script.js", proxiedBody, sentBody, wantedHeaders, t) + mockCall(http.MethodPost, "/js/script.js", responseBody, sentBody, wantedHeaders, t) request, _ := http.NewRequest( http.MethodPost, @@ -115,5 +111,9 @@ func TestProxy(t *testing.T) { Proxy(response, request) + if response.Body.String() != responseBody { + t.Errorf("Invalid returned body. got %q, want %q", response.Body.String(), responseBody) + } + }) }