| 279 | } |
| 280 | |
| 281 | func (c *Compressor) compress(ctx context.Context, encoding string, cref ref, r *http.Request) { |
| 282 | cachePath := cref.key.filePath(c.cacheDir) |
| 283 | var err error |
| 284 | // we want to handle closing either cref.done or cref.err in a defer at the bottom of the stack so that the encoder |
| 285 | // and cache file are both closed first (higher in the defer stack). This prevents data races where waiting HTTP |
| 286 | // handlers start reading the file before all the data has been flushed. |
| 287 | defer func() { |
| 288 | if err != nil { |
| 289 | if rErr := os.Remove(cachePath); rErr != nil { |
| 290 | // nolint: gocritic // best effort, just debug log any errors |
| 291 | c.logger.Debug(ctx, "failed to remove cache file", |
| 292 | slog.F("main_err", err), slog.F("remove_err", rErr), slog.F("cache_path", cachePath)) |
| 293 | } |
| 294 | c.mu.Lock() |
| 295 | delete(c.cache, cref.key) |
| 296 | c.mu.Unlock() |
| 297 | close(cref.err) |
| 298 | return |
| 299 | } |
| 300 | close(cref.done) |
| 301 | }() |
| 302 | |
| 303 | cacheDir := filepath.Dir(cachePath) |
| 304 | err = os.MkdirAll(cacheDir, 0o700) |
| 305 | if err != nil { |
| 306 | c.logger.Error(ctx, "failed to create cache directory", slog.F("cache_dir", cacheDir)) |
| 307 | return |
| 308 | } |
| 309 | |
| 310 | // We will truncate and overwrite any existing files. This is important in the case that we get restarted |
| 311 | // with the same cache dir, possibly with different source files. |
| 312 | cacheFile, err := os.OpenFile(cachePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) |
| 313 | if err != nil { |
| 314 | c.logger.Error(ctx, "failed to open compression cache file", |
| 315 | slog.F("path", cachePath), slog.Error(err)) |
| 316 | return |
| 317 | } |
| 318 | defer cacheFile.Close() |
| 319 | encoder, cleanup := c.getEncoder(encoding, cacheFile) |
| 320 | if encoder == nil { |
| 321 | // can only hit this if there is a programming error |
| 322 | c.logger.Critical(ctx, "got nil encoder", slog.F("encoding", encoding)) |
| 323 | err = xerrors.New("nil encoder") |
| 324 | return |
| 325 | } |
| 326 | defer cleanup() |
| 327 | defer encoder.Close() // ensures we flush, needs to be called before cleanup(), so we defer after it. |
| 328 | |
| 329 | cw := &compressResponseWriter{ |
| 330 | w: encoder, |
| 331 | headers: make(http.Header), // ignored |
| 332 | } |
| 333 | http.FileServer(c.orig).ServeHTTP(cw, r) |
| 334 | if cw.code != http.StatusOK { |
| 335 | // log at debug because this is likely just a 404 |
| 336 | c.logger.Debug(ctx, "file server failed to serve", |
| 337 | slog.F("encoding", encoding), slog.F("url_path", cref.key.urlPath), slog.F("http_code", cw.code)) |
| 338 | // mark the error so that we clean up correctly |