mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-07-12 23:00:55 +02:00
Add write response timeout
This commit is contained in:
@ -33,6 +33,9 @@ issues:
|
|||||||
- linters: [bodyclose]
|
- linters: [bodyclose]
|
||||||
path: ".*_test.go"
|
path: ".*_test.go"
|
||||||
|
|
||||||
|
- linters: [bodyclose]
|
||||||
|
path: "router/timeout_response.go"
|
||||||
|
|
||||||
# False positives on CGO generated code
|
# False positives on CGO generated code
|
||||||
- linters: [staticcheck]
|
- linters: [staticcheck]
|
||||||
text: "SA4000:"
|
text: "SA4000:"
|
||||||
|
@ -3,9 +3,10 @@
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Add
|
### Add
|
||||||
- Add [IMGPROXY_S3_ASSUME_ROLE_EXTERNAL_ID](https://docs.imgproxy.net/latest/configuration/options#IMGPROXY_S3_ASSUME_ROLE_EXTERNAL_ID) config.
|
- Add [IMGPROXY_S3_ASSUME_ROLE_EXTERNAL_ID](https://docs.imgproxy.net/latest/configuration/options#IMGPROXY_S3_ASSUME_ROLE_EXTERNAL_ID) config.
|
||||||
|
- Add [IMGPROXY_WRITE_RESPONSE_TIMEOUT](https://docs.imgproxy.net/latest/configuration/options#IMGPROXY_WRITE_RESPONSE_TIMEOUT) config.
|
||||||
|
- Add [IMGPROXY_REPORT_IO_ERRORS](https://docs.imgproxy.net/latest/configuration/options#IMGPROXY_REPORT_IO_ERRORS) config.
|
||||||
- (pro) Add [colorize](https://docs.imgproxy.net/latest/usage/processing#colorize) processing option.
|
- (pro) Add [colorize](https://docs.imgproxy.net/latest/usage/processing#colorize) processing option.
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Automatically add `http://` scheme to the `IMGPROXY_S3_ENDPOINT` value if it has no scheme.
|
- Automatically add `http://` scheme to the `IMGPROXY_S3_ENDPOINT` value if it has no scheme.
|
||||||
- Trim redundant slashes in the S3 object key.
|
- Trim redundant slashes in the S3 object key.
|
||||||
|
@ -23,6 +23,7 @@ var (
|
|||||||
Bind string
|
Bind string
|
||||||
ReadTimeout int
|
ReadTimeout int
|
||||||
WriteTimeout int
|
WriteTimeout int
|
||||||
|
WriteResponseTimeout int
|
||||||
KeepAliveTimeout int
|
KeepAliveTimeout int
|
||||||
ClientKeepAliveTimeout int
|
ClientKeepAliveTimeout int
|
||||||
DownloadTimeout int
|
DownloadTimeout int
|
||||||
@ -188,6 +189,7 @@ var (
|
|||||||
AirbrakeEnv string
|
AirbrakeEnv string
|
||||||
|
|
||||||
ReportDownloadingErrors bool
|
ReportDownloadingErrors bool
|
||||||
|
ReportIOErrors bool
|
||||||
|
|
||||||
EnableDebugHeaders bool
|
EnableDebugHeaders bool
|
||||||
|
|
||||||
@ -217,6 +219,7 @@ func Reset() {
|
|||||||
Bind = ":8080"
|
Bind = ":8080"
|
||||||
ReadTimeout = 10
|
ReadTimeout = 10
|
||||||
WriteTimeout = 10
|
WriteTimeout = 10
|
||||||
|
WriteResponseTimeout = 10
|
||||||
KeepAliveTimeout = 10
|
KeepAliveTimeout = 10
|
||||||
ClientKeepAliveTimeout = 90
|
ClientKeepAliveTimeout = 90
|
||||||
DownloadTimeout = 5
|
DownloadTimeout = 5
|
||||||
@ -380,6 +383,7 @@ func Reset() {
|
|||||||
AirbrakeEnv = "production"
|
AirbrakeEnv = "production"
|
||||||
|
|
||||||
ReportDownloadingErrors = true
|
ReportDownloadingErrors = true
|
||||||
|
ReportIOErrors = false
|
||||||
|
|
||||||
EnableDebugHeaders = false
|
EnableDebugHeaders = false
|
||||||
|
|
||||||
@ -401,6 +405,7 @@ func Configure() error {
|
|||||||
configurators.String(&Bind, "IMGPROXY_BIND")
|
configurators.String(&Bind, "IMGPROXY_BIND")
|
||||||
configurators.Int(&ReadTimeout, "IMGPROXY_READ_TIMEOUT")
|
configurators.Int(&ReadTimeout, "IMGPROXY_READ_TIMEOUT")
|
||||||
configurators.Int(&WriteTimeout, "IMGPROXY_WRITE_TIMEOUT")
|
configurators.Int(&WriteTimeout, "IMGPROXY_WRITE_TIMEOUT")
|
||||||
|
configurators.Int(&WriteResponseTimeout, "IMGPROXY_WRITE_RESPONSE_TIMEOUT")
|
||||||
configurators.Int(&KeepAliveTimeout, "IMGPROXY_KEEP_ALIVE_TIMEOUT")
|
configurators.Int(&KeepAliveTimeout, "IMGPROXY_KEEP_ALIVE_TIMEOUT")
|
||||||
configurators.Int(&ClientKeepAliveTimeout, "IMGPROXY_CLIENT_KEEP_ALIVE_TIMEOUT")
|
configurators.Int(&ClientKeepAliveTimeout, "IMGPROXY_CLIENT_KEEP_ALIVE_TIMEOUT")
|
||||||
configurators.Int(&DownloadTimeout, "IMGPROXY_DOWNLOAD_TIMEOUT")
|
configurators.Int(&DownloadTimeout, "IMGPROXY_DOWNLOAD_TIMEOUT")
|
||||||
@ -597,6 +602,7 @@ func Configure() error {
|
|||||||
configurators.String(&AirbrakeProjecKey, "IMGPROXY_AIRBRAKE_PROJECT_KEY")
|
configurators.String(&AirbrakeProjecKey, "IMGPROXY_AIRBRAKE_PROJECT_KEY")
|
||||||
configurators.String(&AirbrakeEnv, "IMGPROXY_AIRBRAKE_ENVIRONMENT")
|
configurators.String(&AirbrakeEnv, "IMGPROXY_AIRBRAKE_ENVIRONMENT")
|
||||||
configurators.Bool(&ReportDownloadingErrors, "IMGPROXY_REPORT_DOWNLOADING_ERRORS")
|
configurators.Bool(&ReportDownloadingErrors, "IMGPROXY_REPORT_DOWNLOADING_ERRORS")
|
||||||
|
configurators.Bool(&ReportIOErrors, "IMGPROXY_REPORT_IO_ERRORS")
|
||||||
configurators.Bool(&EnableDebugHeaders, "IMGPROXY_ENABLE_DEBUG_HEADERS")
|
configurators.Bool(&EnableDebugHeaders, "IMGPROXY_ENABLE_DEBUG_HEADERS")
|
||||||
|
|
||||||
configurators.Int(&FreeMemoryInterval, "IMGPROXY_FREE_MEMORY_INTERVAL")
|
configurators.Int(&FreeMemoryInterval, "IMGPROXY_FREE_MEMORY_INTERVAL")
|
||||||
|
@ -143,10 +143,21 @@ func respondWithImage(reqID string, r *http.Request, rw http.ResponseWriter, sta
|
|||||||
|
|
||||||
rw.Header().Set("Content-Length", strconv.Itoa(len(resultData.Data)))
|
rw.Header().Set("Content-Length", strconv.Itoa(len(resultData.Data)))
|
||||||
rw.WriteHeader(statusCode)
|
rw.WriteHeader(statusCode)
|
||||||
rw.Write(resultData.Data)
|
_, err := rw.Write(resultData.Data)
|
||||||
|
|
||||||
|
var ierr *ierrors.Error
|
||||||
|
if err != nil {
|
||||||
|
ierr = ierrors.New(statusCode, fmt.Sprintf("Failed to write response: %s", err), "Failed to write response")
|
||||||
|
ierr.Unexpected = true
|
||||||
|
|
||||||
|
if config.ReportIOErrors {
|
||||||
|
sendErr(r.Context(), "IO", ierr)
|
||||||
|
errorreport.Report(ierr, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
router.LogResponse(
|
router.LogResponse(
|
||||||
reqID, r, statusCode, nil,
|
reqID, r, statusCode, ierr,
|
||||||
log.Fields{
|
log.Fields{
|
||||||
"image_url": originURL,
|
"image_url": originURL,
|
||||||
"processing_options": po,
|
"processing_options": po,
|
||||||
@ -204,14 +215,6 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
if queueSem != nil {
|
|
||||||
acquired := queueSem.TryAcquire(1)
|
|
||||||
if !acquired {
|
|
||||||
panic(ierrors.New(429, "Too many requests", "Too many requests"))
|
|
||||||
}
|
|
||||||
defer queueSem.Release(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
path := r.RequestURI
|
path := r.RequestURI
|
||||||
if queryStart := strings.IndexByte(path, '?'); queryStart >= 0 {
|
if queryStart := strings.IndexByte(path, '?'); queryStart >= 0 {
|
||||||
path = path[:queryStart]
|
path = path[:queryStart]
|
||||||
@ -282,6 +285,14 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if queueSem != nil {
|
||||||
|
acquired := queueSem.TryAcquire(1)
|
||||||
|
if !acquired {
|
||||||
|
panic(ierrors.New(429, "Too many requests", "Too many requests"))
|
||||||
|
}
|
||||||
|
defer queueSem.Release(1)
|
||||||
|
}
|
||||||
|
|
||||||
// The heavy part starts here, so we need to restrict worker number
|
// The heavy part starts here, so we need to restrict worker number
|
||||||
func() {
|
func() {
|
||||||
defer metrics.StartQueueSegment(ctx)()
|
defer metrics.StartQueueSegment(ctx)()
|
||||||
|
@ -95,6 +95,8 @@ func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
req, timeoutCancel := startRequestTimer(req)
|
req, timeoutCancel := startRequestTimer(req)
|
||||||
defer timeoutCancel()
|
defer timeoutCancel()
|
||||||
|
|
||||||
|
rw = newTimeoutResponse(rw)
|
||||||
|
|
||||||
reqID := req.Header.Get(xRequestIDHeader)
|
reqID := req.Header.Get(xRequestIDHeader)
|
||||||
|
|
||||||
if len(reqID) == 0 || !requestIDRe.MatchString(reqID) {
|
if len(reqID) == 0 || !requestIDRe.MatchString(reqID) {
|
||||||
|
43
router/timeout_response.go
Normal file
43
router/timeout_response.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/imgproxy/imgproxy/v3/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type timeoutResponse struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
controller *http.ResponseController
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTimeoutResponse(rw http.ResponseWriter) http.ResponseWriter {
|
||||||
|
return &timeoutResponse{
|
||||||
|
ResponseWriter: rw,
|
||||||
|
controller: http.NewResponseController(rw),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *timeoutResponse) WriteHeader(statusCode int) {
|
||||||
|
rw.withWriteDeadline(func() {
|
||||||
|
rw.ResponseWriter.WriteHeader(statusCode)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *timeoutResponse) Write(b []byte) (int, error) {
|
||||||
|
var (
|
||||||
|
n int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
rw.withWriteDeadline(func() {
|
||||||
|
n, err = rw.ResponseWriter.Write(b)
|
||||||
|
})
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *timeoutResponse) withWriteDeadline(f func()) {
|
||||||
|
rw.controller.SetWriteDeadline(time.Now().Add(time.Duration(config.WriteResponseTimeout) * time.Second))
|
||||||
|
defer rw.controller.SetWriteDeadline(time.Time{})
|
||||||
|
f()
|
||||||
|
}
|
Reference in New Issue
Block a user