diff --git a/cmd/bosun/conf/system.go b/cmd/bosun/conf/system.go index 7cf841762f..8776d5ba06 100644 --- a/cmd/bosun/conf/system.go +++ b/cmd/bosun/conf/system.go @@ -541,10 +541,12 @@ func (sc *SystemConf) AnnotateEnabled() bool { // MakeLink creates a HTML Link based on Bosun's configured Hostname func (sc *SystemConf) MakeLink(path string, v *url.Values) string { u := url.URL{ - Scheme: sc.Scheme, - Host: sc.Hostname, - Path: path, - RawQuery: v.Encode(), + Scheme: sc.Scheme, + Host: sc.Hostname, + Path: path, + } + if v != nil { + u.RawQuery = v.Encode() } return u.String() } diff --git a/cmd/bosun/database/config_data.go b/cmd/bosun/database/config_data.go index bae6c58919..465801ea32 100644 --- a/cmd/bosun/database/config_data.go +++ b/cmd/bosun/database/config_data.go @@ -3,6 +3,7 @@ package database import ( "crypto/md5" "encoding/base64" + "fmt" "github.com/garyburd/redigo/redis" @@ -12,6 +13,9 @@ import ( type ConfigDataAccess interface { SaveTempConfig(text string) (hash string, err error) GetTempConfig(hash string) (text string, err error) + + ShortenLink(fullURL string) (id int, err error) + GetShortLink(id int) (fullURL string, err error) } func (d *dataAccess) Configs() ConfigDataAccess { @@ -46,3 +50,33 @@ func (d *dataAccess) GetTempConfig(hash string) (string, error) { _, err = conn.Do("EXPIRE", key, configLifetime) return dat, slog.Wrap(err) } + +const ( + shortLinkCounterKey = "shortlinkCount" + shortLinksKey = "shortLinks" +) + +func (d *dataAccess) ShortenLink(fullURL string) (id int, err error) { + conn := d.Get() + defer conn.Close() + + newID, err := redis.Int(conn.Do("INCR", shortLinkCounterKey)) + if err != nil { + return 0, slog.Wrap(err) + } + if _, err := conn.Do("HSET", shortLinksKey, fmt.Sprint(newID), fullURL); err != nil { + return 0, slog.Wrap(err) + } + return newID, nil +} + +func (d *dataAccess) GetShortLink(id int) (fullURL string, err error) { + conn := d.Get() + defer conn.Close() + + s, err := redis.String(conn.Do("HGET", shortLinksKey, fmt.Sprint(id))) + if err != nil { + return "", slog.Wrap(err) + } + return s, nil +} diff --git a/cmd/bosun/web/web.go b/cmd/bosun/web/web.go index 12ffc534ba..2edd1ffd3c 100644 --- a/cmd/bosun/web/web.go +++ b/cmd/bosun/web/web.go @@ -8,7 +8,6 @@ import ( "html/template" "io" "io/ioutil" - "net" "net/http" "net/http/httputil" "net/url" @@ -171,6 +170,7 @@ func Listen(httpAddr, httpsAddr, certFile, keyFile string, devMode bool, tsdbHos handle("/api/rule", JSON(Rule), canRunTests).Name("rule_test").Methods(POST) handle("/api/rule/notification/test", JSON(TestHTTPNotification), canRunTests).Name("rule__notification_test").Methods(POST) handle("/api/shorten", JSON(Shorten), canViewDash).Name("shorten") + handle("/s/{id}", JSON(GetShortLink), canViewDash).Name("shortlink") handle("/api/silence/clear", JSON(SilenceClear), canSilence).Name("silence_clear") handle("/api/silence/get", JSON(SilenceGet), canViewDash).Name("silence_get").Methods(GET) handle("/api/silence/set", JSON(SilenceSet), canSilence).Name("silence_set") @@ -406,41 +406,29 @@ func JSON(h func(miniprofiler.Timer, http.ResponseWriter, *http.Request) (interf } func Shorten(_ miniprofiler.Timer, w http.ResponseWriter, r *http.Request) (interface{}, error) { - u := url.URL{ - Scheme: "https", - Host: "www.googleapis.com", - Path: "/urlshortener/v1/url", - } - if schedule.SystemConf.GetShortURLKey() != "" { - u.RawQuery = "key=" + schedule.SystemConf.GetShortURLKey() - } - j, err := json.Marshal(struct { - LongURL string `json:"longUrl"` - }{ - r.Referer(), - }) + id, err := schedule.DataAccess.Configs().ShortenLink(r.Referer()) if err != nil { return nil, err } + return struct { + ID string `json:"id"` + }{schedule.SystemConf.MakeLink(fmt.Sprintf("/s/%d", id), nil)}, nil +} - transport := &http.Transport{ - Dial: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - }).Dial, - TLSHandshakeTimeout: 10 * time.Second, - } - if InternetProxy != nil { - transport.Proxy = http.ProxyURL(InternetProxy) +func GetShortLink(t miniprofiler.Timer, w http.ResponseWriter, r *http.Request) (interface{}, error) { + // on any error or bad param, just redirect to index. Otherwise 302 to stored url + vars := mux.Vars(r) + idv := vars["id"] + id, err := strconv.Atoi(idv) + targetURL := "" + if err != nil { + return Index(t, w, r) } - c := http.Client{Transport: transport} - - req, err := c.Post(u.String(), "application/json", bytes.NewBuffer(j)) + targetURL, err = schedule.DataAccess.Configs().GetShortLink(id) if err != nil { - return nil, err + return Index(t, w, r) } - io.Copy(w, req.Body) - req.Body.Close() + http.Redirect(w, r, targetURL, 302) return nil, nil }