Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(imageproxy): switch to get and copy body instead of reverse proxy #340

Merged
merged 1 commit into from
Jun 27, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions src/config/defaults.go
Original file line number Diff line number Diff line change
@@ -25,11 +25,7 @@ func New() Config {
},
},
ImageProxy: ImageProxy{
Timeouts: ImageProxyTimeouts{
Dial: 3 * time.Second,
KeepAlive: 3 * time.Second,
TLSHandshake: 2 * time.Second,
},
Timeout: 3 * time.Second,
},
},
Categories: map[category.Name]Category{
16 changes: 4 additions & 12 deletions src/config/load.go
Original file line number Diff line number Diff line change
@@ -85,12 +85,8 @@ func (c Config) getReader() ReaderConfig {
Redis: c.Server.Cache.Redis,
},
ImageProxy: ReaderImageProxy{
Salt: c.Server.ImageProxy.Salt,
Timeouts: ReaderImageProxyTimeouts{
Dial: moretime.ConvertToFancyTime(c.Server.ImageProxy.Timeouts.Dial),
KeepAlive: moretime.ConvertToFancyTime(c.Server.ImageProxy.Timeouts.KeepAlive),
TLSHandshake: moretime.ConvertToFancyTime(c.Server.ImageProxy.Timeouts.TLSHandshake),
},
Salt: c.Server.ImageProxy.Salt,
Timeout: moretime.ConvertToFancyTime(c.Server.ImageProxy.Timeout),
},
},
// Initialize the categories map.
@@ -153,12 +149,8 @@ func (c *Config) fromReader(rc ReaderConfig) {
Redis: rc.Server.Cache.Redis,
},
ImageProxy: ImageProxy{
Salt: rc.Server.ImageProxy.Salt,
Timeouts: ImageProxyTimeouts{
Dial: moretime.ConvertFromFancyTime(rc.Server.ImageProxy.Timeouts.Dial),
KeepAlive: moretime.ConvertFromFancyTime(rc.Server.ImageProxy.Timeouts.KeepAlive),
TLSHandshake: moretime.ConvertFromFancyTime(rc.Server.ImageProxy.Timeouts.TLSHandshake),
},
Salt: rc.Server.ImageProxy.Salt,
Timeout: moretime.ConvertFromFancyTime(rc.Server.ImageProxy.Timeout),
},
},
// Initialize the categories map.
24 changes: 6 additions & 18 deletions src/config/structs_server.go
Original file line number Diff line number Diff line change
@@ -76,26 +76,14 @@ type Redis struct {
}

// ReaderProxy is format in which the config is read from the config file and environment variables.
type ReaderImageProxy struct {
Salt string `koanf:"salt"`
Timeouts ReaderImageProxyTimeouts `koanf:"timeouts"`
}
type ImageProxy struct {
Salt string
Timeouts ImageProxyTimeouts
}

// ReaderProxyTimeouts is format in which the config is read from the config file and environment variables.
// In <number><unit> format.
// Example: 1s, 1m, 1h, 1d, 1w, 1M, 1y.
// If unit is not specified, it is assumed to be milliseconds.
type ReaderImageProxyTimeouts struct {
Dial string `koanf:"dial"`
KeepAlive string `koanf:"keepalive"`
TLSHandshake string `koanf:"tlshandshake"`
type ReaderImageProxy struct {
Salt string `koanf:"salt"`
Timeout string `koanf:"timeout"`
}
type ImageProxyTimeouts struct {
Dial time.Duration
KeepAlive time.Duration
TLSHandshake time.Duration
type ImageProxy struct {
Salt string
Timeout time.Duration
}
45 changes: 0 additions & 45 deletions src/router/routes/responses.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package routes

import (
"encoding/json"
"fmt"
"net/http"

"github.com/hearchco/agent/src/search/result"
)

@@ -29,44 +25,3 @@ type SuggestionsResponse struct {

Suggestions []result.Suggestion `json:"suggestions"`
}

func writeResponse(w http.ResponseWriter, status int, body string) error {
w.WriteHeader(status)
_, err := w.Write([]byte(body))
return err
}

func writeResponseJSON(w http.ResponseWriter, status int, body any) error {
res, err := json.Marshal(body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, werr := w.Write([]byte("internal server error"))
if werr != nil {
return fmt.Errorf("%w: %w", werr, err)
}
return err
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_, err = w.Write(res)
return err
}

func writeResponseSuggestions(w http.ResponseWriter, status int, query string, suggestions []string) error {
jsonStruct := [...]any{query, suggestions}
res, err := json.Marshal(jsonStruct)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, werr := w.Write([]byte("internal server error"))
if werr != nil {
return fmt.Errorf("%w: %w", werr, err)
}
return err
}

w.Header().Set("Content-Type", "application/x-suggestions+json")
w.WriteHeader(status)
_, err = w.Write(res)
return err
}
54 changes: 34 additions & 20 deletions src/router/routes/route_proxy.go
Original file line number Diff line number Diff line change
@@ -2,21 +2,20 @@ package routes

import (
"fmt"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"strings"
"time"

"github.com/rs/zerolog/log"

"github.com/hearchco/agent/src/config"
"github.com/hearchco/agent/src/search/useragent"
"github.com/hearchco/agent/src/utils/anonymize"
"github.com/hearchco/agent/src/utils/moreurls"
)

func routeProxy(w http.ResponseWriter, r *http.Request, salt string, timeouts config.ImageProxyTimeouts) error {
func routeProxy(w http.ResponseWriter, r *http.Request, salt string, timeout time.Duration) error {
// Parse the form.
err := r.ParseForm()
if err != nil {
@@ -93,15 +92,23 @@ func routeProxy(w http.ResponseWriter, r *http.Request, salt string, timeouts co
Msg("Created a new anon request")

// Create reverse proxy with timeout.
rp := createReverseProxy(timeouts)

// Proxy the request.
log.Debug().
Str("url", target.String()).
Msg("Proxying request")
rp.ServeHTTP(w, &nr) // Use the new request.

return nil
// Use the new request.
resp, err := requestResponse(&nr, timeout)
if err != nil {
// Server error.
log.Debug().
Err(err).
Str("url", target.String()).
Msg("Failed to proxy request")
return writeResponse(w, http.StatusInternalServerError, fmt.Sprintf("failed to proxy request: %v", err))
}

// Proxy the response.
return writeResponseImageProxy(w, resp)
}

func getUrlToVerifyAndToProxy(urlParam string, favicon bool) (string, string, error) {
@@ -150,7 +157,6 @@ func createAnonRequest(r *http.Request, target *url.URL) http.Request {
Method: http.MethodGet,
URL: target,
Host: target.Host,
RequestURI: target.RequestURI(),
Proto: "HTTP/2",
ProtoMajor: 2,
ProtoMinor: 0,
@@ -169,15 +175,23 @@ func createAnonRequest(r *http.Request, target *url.URL) http.Request {
}
}

func createReverseProxy(timeouts config.ImageProxyTimeouts) httputil.ReverseProxy {
// Create reverse proxy with timeout.
rp := httputil.ReverseProxy{Director: func(r *http.Request) {}}
rp.Transport = &http.Transport{
DialContext: (&net.Dialer{
Timeout: timeouts.Dial,
KeepAlive: timeouts.KeepAlive,
}).DialContext,
TLSHandshakeTimeout: timeouts.TLSHandshake,
func requestResponse(r *http.Request, timeout time.Duration) (*http.Response, error) {
// Create a reverse proxy.
client := http.Client{Timeout: timeout}
resp, err := client.Do(r)
if err != nil {
return nil, err
}

// Check if the response is OK.
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("response status code is not OK: %v", resp.StatusCode)
}
return rp

// Check if the response is of image type.
if !strings.HasPrefix(resp.Header.Get("Content-Type"), "image/") {
return nil, fmt.Errorf("response content type is not an image: %v", resp.Header.Get("Content-Type"))
}

return resp, nil
}
2 changes: 1 addition & 1 deletion src/router/routes/setup.go
Original file line number Diff line number Diff line change
@@ -82,7 +82,7 @@ func Setup(mux *chi.Mux, ver string, db cache.DB, conf config.Config) {

// /proxy
mux.Get("/proxy", func(w http.ResponseWriter, r *http.Request) {
err := routeProxy(w, r, conf.Server.ImageProxy.Salt, conf.Server.ImageProxy.Timeouts)
err := routeProxy(w, r, conf.Server.ImageProxy.Salt, conf.Server.ImageProxy.Timeout)
if err != nil {
log.Error().
Err(err).
58 changes: 58 additions & 0 deletions src/router/routes/writers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package routes

import (
"encoding/json"
"fmt"
"io"
"net/http"
)

func writeResponse(w http.ResponseWriter, status int, body string) error {
w.WriteHeader(status)
_, err := w.Write([]byte(body))
return err
}

func writeResponseJSON(w http.ResponseWriter, status int, body any) error {
res, err := json.Marshal(body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, werr := w.Write([]byte("internal server error"))
if werr != nil {
return fmt.Errorf("%w: %w", werr, err)
}
return err
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_, err = w.Write(res)
return err
}

func writeResponseSuggestions(w http.ResponseWriter, status int, query string, suggestions []string) error {
jsonStruct := [...]any{query, suggestions}
res, err := json.Marshal(jsonStruct)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, werr := w.Write([]byte("internal server error"))
if werr != nil {
return fmt.Errorf("%w: %w", werr, err)
}
return err
}

w.Header().Set("Content-Type", "application/x-suggestions+json")
w.WriteHeader(status)
_, err = w.Write(res)
return err
}

func writeResponseImageProxy(w http.ResponseWriter, resp *http.Response) error {
w.Header().Set("Content-Encoding", resp.Header.Get("Content-Encoding"))
w.Header().Set("Content-Length", resp.Header.Get("Content-Length"))
w.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
w.WriteHeader(resp.StatusCode)
_, err := io.Copy(w, resp.Body)
return err
}