mirror of
https://github.com/yusing/godoxy.git
synced 2025-06-04 02:42:34 +02:00
fixed and improved favicon retrieving
This commit is contained in:
parent
639b03f820
commit
63d9be069f
1 changed files with 62 additions and 11 deletions
|
@ -1,11 +1,14 @@
|
||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -13,7 +16,9 @@ import (
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
"github.com/vincent-petithory/dataurl"
|
"github.com/vincent-petithory/dataurl"
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
||||||
|
"github.com/yusing/go-proxy/internal/homepage"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
|
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
||||||
route "github.com/yusing/go-proxy/internal/route/types"
|
route "github.com/yusing/go-proxy/internal/route/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,6 +47,10 @@ func (c *content) WriteHeader(statusCode int) {
|
||||||
c.status = statusCode
|
c.status = statusCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *content) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
return nil, nil, errors.New("not supported")
|
||||||
|
}
|
||||||
|
|
||||||
// GetFavIcon returns the favicon of the route
|
// GetFavIcon returns the favicon of the route
|
||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
|
@ -136,7 +145,18 @@ func getIconAbsolute(url string) ([]byte, int, string) {
|
||||||
return icon, http.StatusOK, ""
|
return icon, http.StatusOK, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func findIcon(r route.HTTPRoute, req *http.Request, path string) (icon []byte, status int, errMsg string) {
|
var nameSanitizer = strings.NewReplacer(
|
||||||
|
"_", "-",
|
||||||
|
" ", "-",
|
||||||
|
"(", "",
|
||||||
|
")", "",
|
||||||
|
)
|
||||||
|
|
||||||
|
func sanitizeName(name string) string {
|
||||||
|
return strings.ToLower(nameSanitizer.Replace(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func findIcon(r route.HTTPRoute, req *http.Request, uri string) (icon []byte, status int, errMsg string) {
|
||||||
key := r.TargetName()
|
key := r.TargetName()
|
||||||
icon, ok := loadIconCache(key)
|
icon, ok := loadIconCache(key)
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -146,41 +166,72 @@ func findIcon(r route.HTTPRoute, req *http.Request, path string) (icon []byte, s
|
||||||
return icon, http.StatusOK, ""
|
return icon, http.StatusOK, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
icon, status, errMsg = findIconSlow(r, req, path)
|
icon, status, errMsg = findIconSlow(r, req, uri)
|
||||||
|
if icon == nil {
|
||||||
|
// fallback to dashboard icon
|
||||||
|
icon, status, errMsg = getIconAbsolute(homepage.DashboardIconBaseURL + "png/" + sanitizeName(r.TargetName()) + ".png")
|
||||||
|
}
|
||||||
// set even if error (nil)
|
// set even if error (nil)
|
||||||
storeIconCache(key, icon)
|
storeIconCache(key, icon)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func findIconSlow(r route.HTTPRoute, req *http.Request, path string) (icon []byte, status int, errMsg string) {
|
func findIconSlow(r route.HTTPRoute, req *http.Request, uri string) (icon []byte, status int, errMsg string) {
|
||||||
c := newContent()
|
c := newContent()
|
||||||
ctx, cancel := context.WithTimeoutCause(req.Context(), 3*time.Second, errors.New("favicon request timeout"))
|
ctx, cancel := context.WithTimeoutCause(req.Context(), 3*time.Second, errors.New("favicon request timeout"))
|
||||||
defer cancel()
|
defer cancel()
|
||||||
newReq := req.WithContext(ctx)
|
newReq := req.WithContext(ctx)
|
||||||
newReq.URL.Path = path
|
newReq.Header.Set("Accept-Encoding", "identity") // disable compression
|
||||||
newReq.URL.RawPath = path
|
u, err := url.ParseRequestURI(uri)
|
||||||
newReq.URL.RawQuery = ""
|
if err != nil {
|
||||||
newReq.RequestURI = path
|
logging.Error().Err(err).
|
||||||
|
Str("route", r.TargetName()).
|
||||||
|
Str("path", uri).
|
||||||
|
Msg("failed to parse uri")
|
||||||
|
return nil, http.StatusInternalServerError, "cannot parse uri"
|
||||||
|
}
|
||||||
|
newReq.URL.Path = u.Path
|
||||||
|
newReq.URL.RawPath = u.RawPath
|
||||||
|
newReq.URL.RawQuery = u.RawQuery
|
||||||
|
newReq.RequestURI = u.String()
|
||||||
r.ServeHTTP(c, newReq)
|
r.ServeHTTP(c, newReq)
|
||||||
if c.status != http.StatusOK {
|
if c.status != http.StatusOK {
|
||||||
|
switch c.status {
|
||||||
|
case 0:
|
||||||
|
return nil, http.StatusBadGateway, "connection error"
|
||||||
|
default:
|
||||||
|
if loc := c.Header().Get("Location"); loc != "" {
|
||||||
|
if loc == newReq.URL.Path {
|
||||||
|
return nil, http.StatusBadGateway, "circular redirect"
|
||||||
|
}
|
||||||
|
logging.Debug().Str("route", r.TargetName()).
|
||||||
|
Str("from", uri).
|
||||||
|
Str("to", loc).
|
||||||
|
Msg("favicon redirect")
|
||||||
|
return findIconSlow(r, req, loc)
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil, c.status, "upstream error: " + http.StatusText(c.status)
|
return nil, c.status, "upstream error: " + http.StatusText(c.status)
|
||||||
}
|
}
|
||||||
// return icon data
|
// return icon data
|
||||||
if path != "/" {
|
if !gphttp.GetContentType(c.header).IsHTML() {
|
||||||
return c.data, http.StatusOK, ""
|
return c.data, http.StatusOK, ""
|
||||||
}
|
}
|
||||||
// try extract from "link[rel=icon]" from path "/"
|
// try extract from "link[rel=icon]" from path "/"
|
||||||
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(c.data))
|
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(c.data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logging.Error().Err(err).
|
||||||
|
Str("route", r.TargetName()).
|
||||||
|
Msg("failed to parse html")
|
||||||
return nil, http.StatusInternalServerError, "internal error"
|
return nil, http.StatusInternalServerError, "internal error"
|
||||||
}
|
}
|
||||||
ele := doc.Find("link[rel=icon]").First()
|
ele := doc.Find("head > link[rel=icon]").First()
|
||||||
if ele.Length() == 0 {
|
if ele.Length() == 0 {
|
||||||
return nil, http.StatusNotFound, "icon not found"
|
return nil, http.StatusNotFound, "icon element not found"
|
||||||
}
|
}
|
||||||
href := ele.AttrOr("href", "")
|
href := ele.AttrOr("href", "")
|
||||||
if href == "" {
|
if href == "" {
|
||||||
return nil, http.StatusNotFound, "icon not found"
|
return nil, http.StatusNotFound, "icon href not found"
|
||||||
}
|
}
|
||||||
// https://en.wikipedia.org/wiki/Data_URI_scheme
|
// https://en.wikipedia.org/wiki/Data_URI_scheme
|
||||||
if strings.HasPrefix(href, "data:image/") {
|
if strings.HasPrefix(href, "data:image/") {
|
||||||
|
|
Loading…
Add table
Reference in a new issue