diff --git a/src/config/defaults.go b/src/config/defaults.go index d06be882..fb5ccd9a 100644 --- a/src/config/defaults.go +++ b/src/config/defaults.go @@ -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{ diff --git a/src/config/load.go b/src/config/load.go index d80ab865..fc3fad91 100644 --- a/src/config/load.go +++ b/src/config/load.go @@ -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. diff --git a/src/config/structs_server.go b/src/config/structs_server.go index 6385b577..b0184eeb 100644 --- a/src/config/structs_server.go +++ b/src/config/structs_server.go @@ -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 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 } diff --git a/src/router/routes/responses.go b/src/router/routes/responses.go index ae1b43c8..4fead5d9 100644 --- a/src/router/routes/responses.go +++ b/src/router/routes/responses.go @@ -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 -} diff --git a/src/router/routes/route_proxy.go b/src/router/routes/route_proxy.go index face68db..0245b8c9 100644 --- a/src/router/routes/route_proxy.go +++ b/src/router/routes/route_proxy.go @@ -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 } diff --git a/src/router/routes/setup.go b/src/router/routes/setup.go index eaf6cbc1..05f8e1b3 100644 --- a/src/router/routes/setup.go +++ b/src/router/routes/setup.go @@ -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). diff --git a/src/router/routes/writers.go b/src/router/routes/writers.go new file mode 100644 index 00000000..f32b2dfa --- /dev/null +++ b/src/router/routes/writers.go @@ -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 +}