From 8adfa27bd6c4500d2ccbc6366fa8dce53cea2cef Mon Sep 17 00:00:00 2001 From: Milos Stojanovic Date: Wed, 17 Nov 2021 12:13:32 +0100 Subject: [PATCH] cont --- .env.example | 7 ++++ .gitignore | 2 + README.md | 20 ++++++++++ go.mod | 8 ++++ go.sum | 4 ++ main.go | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 147 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d09c2ae --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +ADDR=":8888" +LINK_PREVIEW_KEY="123456" + +# optional ssl certificates +SSL_CERT="cert.pem" +SSL_KEY="cert.key" + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..163ad08 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +v1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..dfba813 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# LinkPreview.net Proxy Server in Go + + +How to use: + +1. Download executable +2. Create .env file configuration, see .env.example + + +Sample .env file: + +``` +ADDR="localhost:8000" +LINK_PREVIEW_KEY="123456" +``` + +3. Start your server + + + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..04589f8 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module linkpreview.net/proxy/v1 + +go 1.17 + +require ( + github.com/joho/godotenv v1.4.0 + github.com/muesli/cache2go v0.0.0-20211005105910-8e46465cca4a +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..58f0c61 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= +github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/muesli/cache2go v0.0.0-20211005105910-8e46465cca4a h1:IZxQOY9gAiiGGuEdlOBnqaC3yumj8UvyQluBNqGP2Ek= +github.com/muesli/cache2go v0.0.0-20211005105910-8e46465cca4a/go.mod h1:WERUkUryfUWlrHnFSO/BEUZ+7Ns8aZy7iVOGewxKzcc= diff --git a/main.go b/main.go new file mode 100644 index 0000000..d2ea88e --- /dev/null +++ b/main.go @@ -0,0 +1,106 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/muesli/cache2go" +) + +const linkpreviewAPI = "https://api.linkpreview.net" + +var linkpreviewKey = "" + +var cache *cache2go.CacheTable + +type cachedResponse struct { + body []byte + status int +} + +type lpreq struct { + Key string `json:"key"` + Q string `json:"q"` + Fields string `json:"fields"` +} + +func main() { + + err := godotenv.Load() + if err != nil { + log.Fatal("Error loading .env file") + } + + addr := os.Getenv("ADDR") + linkpreviewKey = os.Getenv("LINK_PREVIEW_KEY") + + cache = cache2go.Cache("myCache") + + http.HandleFunc("/", proxyHandler) + + if os.Getenv("SSL_CERT") != "" && os.Getenv("SSL_KEY") != "" { + fmt.Printf("Proxy secure server started on https://%s\n", addr) + if err := http.ListenAndServeTLS(addr, os.Getenv("SSL_CERT"), os.Getenv("SSL_KEY"), nil); err != nil { + panic(err) + } + } + + fmt.Printf("Proxy server started on http://%s\n", addr) + if err := http.ListenAndServe(addr, nil); err != nil { + panic(err) + } + +} + +func proxyHandler(w http.ResponseWriter, r *http.Request) { + + query := r.URL.Query().Get("q") + + // keep results in cache for one day (based on the key scheme) + cacheKey := fmt.Sprintf("%s%d", query, time.Now().Day()) + + cached, err := cache.Value(cacheKey) + if err == nil { + fmt.Println("Serving from Cache: " + query) + w.WriteHeader(cached.Data().(*cachedResponse).status) + w.Write(cached.Data().(*cachedResponse).body) + return + } + + body, _ := json.Marshal(&lpreq{ + Key: linkpreviewKey, + Q: query, + Fields: "title,description,image,url", // see https://docs.linkpreview.net/#query-parameters + }) + + fmt.Println("Requesting from the API: " + query) + client := &http.Client{} + req, _ := http.NewRequest("POST", linkpreviewAPI, bytes.NewBuffer(body)) + resp, err := client.Do(req) + if err != nil { + http.Error(w, "Server Error", http.StatusInternalServerError) + return + } + defer resp.Body.Close() + + cr := cachedResponse{} + cr.body, err = io.ReadAll(resp.Body) + cr.status = resp.StatusCode + if err != nil { + http.Error(w, "Server Error", http.StatusInternalServerError) + return + } + + w.WriteHeader(cr.status) + w.Write(cr.body) + + // add to cache and automatically expire unused after one day + cache.Add(cacheKey, 24*time.Hour, &cr) +}