loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add direct serving of package content (#25543)

Fixes #24723

Direct serving of content aka HTTP redirect is not mentioned in any of
the package registry specs but lots of official registries do that so it
should be supported by the usual clients.

authored by

KN4CK3R and committed by
GitHub
c8904547 f1cb461c

+195 -235
+10
modules/packages/content_store.go
··· 5 5 6 6 import ( 7 7 "io" 8 + "net/url" 8 9 "path" 9 10 "strings" 10 11 12 + "code.gitea.io/gitea/modules/setting" 11 13 "code.gitea.io/gitea/modules/storage" 12 14 "code.gitea.io/gitea/modules/util" 13 15 ) ··· 29 31 // Get gets a package blob 30 32 func (s *ContentStore) Get(key BlobHash256Key) (storage.Object, error) { 31 33 return s.store.Open(KeyToRelativePath(key)) 34 + } 35 + 36 + func (s *ContentStore) ShouldServeDirect() bool { 37 + return setting.Packages.Storage.MinioConfig.ServeDirect 38 + } 39 + 40 + func (s *ContentStore) GetServeDirectURL(key BlobHash256Key, filename string) (*url.URL, error) { 41 + return s.store.URL(KeyToRelativePath(key), filename) 32 42 } 33 43 34 44 // FIXME: Workaround to be removed in v1.20
+4 -12
routers/api/packages/alpine/alpine.go
··· 68 68 return 69 69 } 70 70 71 - s, pf, err := packages_service.GetFileStreamByPackageVersion( 71 + s, u, pf, err := packages_service.GetFileStreamByPackageVersion( 72 72 ctx, 73 73 pv, 74 74 &packages_service.PackageFileInfo{ ··· 84 84 } 85 85 return 86 86 } 87 - defer s.Close() 88 87 89 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 90 - Filename: pf.Name, 91 - LastModified: pf.CreatedUnix.AsLocalTime(), 92 - }) 88 + helper.ServePackageFile(ctx, s, u, pf) 93 89 } 94 90 95 91 func UploadPackageFile(ctx *context.Context) { ··· 200 196 return 201 197 } 202 198 203 - s, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) 199 + s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) 204 200 if err != nil { 205 201 if errors.Is(err, util.ErrNotExist) { 206 202 apiError(ctx, http.StatusNotFound, err) ··· 209 205 } 210 206 return 211 207 } 212 - defer s.Close() 213 208 214 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 215 - Filename: pf.Name, 216 - LastModified: pf.CreatedUnix.AsLocalTime(), 217 - }) 209 + helper.ServePackageFile(ctx, s, u, pf) 218 210 } 219 211 220 212 func DeletePackageFile(ctx *context.Context) {
+2 -6
routers/api/packages/cargo/cargo.go
··· 165 165 166 166 // DownloadPackageFile serves the content of a package 167 167 func DownloadPackageFile(ctx *context.Context) { 168 - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 168 + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 169 169 ctx, 170 170 &packages_service.PackageInfo{ 171 171 Owner: ctx.Package.Owner, ··· 185 185 apiError(ctx, http.StatusInternalServerError, err) 186 186 return 187 187 } 188 - defer s.Close() 189 188 190 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 191 - Filename: pf.Name, 192 - LastModified: pf.CreatedUnix.AsLocalTime(), 193 - }) 189 + helper.ServePackageFile(ctx, s, u, pf) 194 190 } 195 191 196 192 // https://doc.rust-lang.org/cargo/reference/registries.html#publish
+2 -6
routers/api/packages/chef/chef.go
··· 341 341 342 342 pf := pd.Files[0].File 343 343 344 - s, _, err := packages_service.GetPackageFileStream(ctx, pf) 344 + s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) 345 345 if err != nil { 346 346 apiError(ctx, http.StatusInternalServerError, err) 347 347 return 348 348 } 349 - defer s.Close() 350 349 351 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 352 - Filename: pf.Name, 353 - LastModified: pf.CreatedUnix.AsLocalTime(), 354 - }) 350 + helper.ServePackageFile(ctx, s, u, pf) 355 351 } 356 352 357 353 // https://github.com/chef/chef/blob/main/knife/lib/chef/knife/supermarket_unshare.rb
+2 -6
routers/api/packages/composer/composer.go
··· 162 162 163 163 // DownloadPackageFile serves the content of a package 164 164 func DownloadPackageFile(ctx *context.Context) { 165 - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 165 + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 166 166 ctx, 167 167 &packages_service.PackageInfo{ 168 168 Owner: ctx.Package.Owner, ··· 182 182 apiError(ctx, http.StatusInternalServerError, err) 183 183 return 184 184 } 185 - defer s.Close() 186 185 187 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 188 - Filename: pf.Name, 189 - LastModified: pf.CreatedUnix.AsLocalTime(), 190 - }) 186 + helper.ServePackageFile(ctx, s, u, pf) 191 187 } 192 188 193 189 // UploadPackage creates a new package
+2 -6
routers/api/packages/conan/conan.go
··· 453 453 return 454 454 } 455 455 456 - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 456 + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 457 457 ctx, 458 458 &packages_service.PackageInfo{ 459 459 Owner: ctx.Package.Owner, ··· 474 474 apiError(ctx, http.StatusInternalServerError, err) 475 475 return 476 476 } 477 - defer s.Close() 478 477 479 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 480 - Filename: pf.Name, 481 - LastModified: pf.CreatedUnix.AsLocalTime(), 482 - }) 478 + helper.ServePackageFile(ctx, s, u, pf) 483 479 } 484 480 485 481 // DeleteRecipeV1 deletes the requested recipe(s)
+2 -6
routers/api/packages/conda/conda.go
··· 292 292 293 293 pf := pfs[0] 294 294 295 - s, _, err := packages_service.GetPackageFileStream(ctx, pf) 295 + s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) 296 296 if err != nil { 297 297 apiError(ctx, http.StatusInternalServerError, err) 298 298 return 299 299 } 300 - defer s.Close() 301 300 302 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 303 - Filename: pf.Name, 304 - LastModified: pf.CreatedUnix.AsLocalTime(), 305 - }) 301 + helper.ServePackageFile(ctx, s, u, pf) 306 302 }
+32 -32
routers/api/packages/container/container.go
··· 482 482 return 483 483 } 484 484 485 - s, _, err := packages_service.GetPackageFileStream(ctx, blob.File) 486 - if err != nil { 487 - apiError(ctx, http.StatusInternalServerError, err) 488 - return 489 - } 490 - defer s.Close() 491 - 492 - setResponseHeaders(ctx.Resp, &containerHeaders{ 493 - ContentDigest: blob.Properties.GetByName(container_module.PropertyDigest), 494 - ContentType: blob.Properties.GetByName(container_module.PropertyMediaType), 495 - ContentLength: blob.Blob.Size, 496 - Status: http.StatusOK, 497 - }) 498 - if _, err := io.Copy(ctx.Resp, s); err != nil { 499 - log.Error("Error whilst copying content to response: %v", err) 500 - } 485 + serveBlob(ctx, blob) 501 486 } 502 487 503 488 // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#deleting-blobs ··· 636 621 return 637 622 } 638 623 639 - s, _, err := packages_service.GetPackageFileStream(ctx, manifest.File) 640 - if err != nil { 641 - apiError(ctx, http.StatusInternalServerError, err) 642 - return 643 - } 644 - defer s.Close() 645 - 646 - setResponseHeaders(ctx.Resp, &containerHeaders{ 647 - ContentDigest: manifest.Properties.GetByName(container_module.PropertyDigest), 648 - ContentType: manifest.Properties.GetByName(container_module.PropertyMediaType), 649 - ContentLength: manifest.Blob.Size, 650 - Status: http.StatusOK, 651 - }) 652 - if _, err := io.Copy(ctx.Resp, s); err != nil { 653 - log.Error("Error whilst copying content to response: %v", err) 654 - } 624 + serveBlob(ctx, manifest) 655 625 } 656 626 657 627 // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#deleting-tags ··· 684 654 setResponseHeaders(ctx.Resp, &containerHeaders{ 685 655 Status: http.StatusAccepted, 686 656 }) 657 + } 658 + 659 + func serveBlob(ctx *context.Context, pfd *packages_model.PackageFileDescriptor) { 660 + s, u, _, err := packages_service.GetPackageBlobStream(ctx, pfd.File, pfd.Blob) 661 + if err != nil { 662 + apiError(ctx, http.StatusInternalServerError, err) 663 + return 664 + } 665 + 666 + headers := &containerHeaders{ 667 + ContentDigest: pfd.Properties.GetByName(container_module.PropertyDigest), 668 + ContentType: pfd.Properties.GetByName(container_module.PropertyMediaType), 669 + ContentLength: pfd.Blob.Size, 670 + Status: http.StatusOK, 671 + } 672 + 673 + if u != nil { 674 + headers.Status = http.StatusTemporaryRedirect 675 + headers.Location = u.String() 676 + 677 + setResponseHeaders(ctx.Resp, headers) 678 + return 679 + } 680 + 681 + defer s.Close() 682 + 683 + setResponseHeaders(ctx.Resp, headers) 684 + if _, err := io.Copy(ctx.Resp, s); err != nil { 685 + log.Error("Error whilst copying content to response: %v", err) 686 + } 687 687 } 688 688 689 689 // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#content-discovery
+2 -6
routers/api/packages/cran/cran.go
··· 249 249 return 250 250 } 251 251 252 - s, _, err := packages_service.GetPackageFileStream(ctx, pf) 252 + s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) 253 253 if err != nil { 254 254 if errors.Is(err, util.ErrNotExist) { 255 255 apiError(ctx, http.StatusNotFound, err) ··· 258 258 } 259 259 return 260 260 } 261 - defer s.Close() 262 261 263 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 264 - Filename: pf.Name, 265 - LastModified: pf.CreatedUnix.AsLocalTime(), 266 - }) 262 + helper.ServePackageFile(ctx, s, u, pf) 267 263 }
+6 -15
routers/api/packages/debian/debian.go
··· 59 59 key += "|" + component + "|" + architecture 60 60 } 61 61 62 - s, pf, err := packages_service.GetFileStreamByPackageVersion( 62 + s, u, pf, err := packages_service.GetFileStreamByPackageVersion( 63 63 ctx, 64 64 pv, 65 65 &packages_service.PackageFileInfo{ ··· 75 75 } 76 76 return 77 77 } 78 - defer s.Close() 79 78 80 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 81 - Filename: pf.Name, 82 - LastModified: pf.CreatedUnix.AsLocalTime(), 83 - }) 79 + helper.ServePackageFile(ctx, s, u, pf) 84 80 } 85 81 86 82 // https://wiki.debian.org/DebianRepository/Format#indices_acquisition_via_hashsums_.28by-hash.29 ··· 110 106 return 111 107 } 112 108 113 - s, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) 109 + s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) 114 110 if err != nil { 115 111 if errors.Is(err, util.ErrNotExist) { 116 112 apiError(ctx, http.StatusNotFound, err) ··· 119 115 } 120 116 return 121 117 } 122 - defer s.Close() 123 118 124 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 125 - Filename: pf.Name, 126 - LastModified: pf.CreatedUnix.AsLocalTime(), 127 - }) 119 + helper.ServePackageFile(ctx, s, u, pf) 128 120 } 129 121 130 122 func UploadPackageFile(ctx *context.Context) { ··· 217 209 name := ctx.Params("name") 218 210 version := ctx.Params("version") 219 211 220 - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 212 + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 221 213 ctx, 222 214 &packages_service.PackageInfo{ 223 215 Owner: ctx.Package.Owner, ··· 238 230 } 239 231 return 240 232 } 241 - defer s.Close() 242 233 243 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 234 + helper.ServePackageFile(ctx, s, u, pf, &context.ServeHeaderOptions{ 244 235 ContentType: "application/vnd.debian.binary-package", 245 236 Filename: pf.Name, 246 237 LastModified: pf.CreatedUnix.AsLocalTime(),
+2 -6
routers/api/packages/generic/generic.go
··· 30 30 31 31 // DownloadPackageFile serves the specific generic package. 32 32 func DownloadPackageFile(ctx *context.Context) { 33 - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 33 + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 34 34 ctx, 35 35 &packages_service.PackageInfo{ 36 36 Owner: ctx.Package.Owner, ··· 50 50 apiError(ctx, http.StatusInternalServerError, err) 51 51 return 52 52 } 53 - defer s.Close() 54 53 55 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 56 - Filename: pf.Name, 57 - LastModified: pf.CreatedUnix.AsLocalTime(), 58 - }) 54 + helper.ServePackageFile(ctx, s, u, pf) 59 55 } 60 56 61 57 // UploadPackage uploads the specific generic package.
+2 -6
routers/api/packages/goproxy/goproxy.go
··· 105 105 return 106 106 } 107 107 108 - s, _, err := packages_service.GetPackageFileStream(ctx, pfs[0]) 108 + s, u, _, err := packages_service.GetPackageFileStream(ctx, pfs[0]) 109 109 if err != nil { 110 110 if errors.Is(err, util.ErrNotExist) { 111 111 apiError(ctx, http.StatusNotFound, err) ··· 114 114 } 115 115 return 116 116 } 117 - defer s.Close() 118 117 119 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 120 - Filename: pfs[0].Name, 121 - LastModified: pfs[0].CreatedUnix.AsLocalTime(), 122 - }) 118 + helper.ServePackageFile(ctx, s, u, pfs[0]) 123 119 } 124 120 125 121 func resolvePackage(ctx *context.Context, ownerID int64, name, version string) (*packages_model.PackageVersion, error) {
+2 -6
routers/api/packages/helm/helm.go
··· 121 121 return 122 122 } 123 123 124 - s, pf, err := packages_service.GetFileStreamByPackageVersion( 124 + s, u, pf, err := packages_service.GetFileStreamByPackageVersion( 125 125 ctx, 126 126 pvs[0], 127 127 &packages_service.PackageFileInfo{ ··· 136 136 apiError(ctx, http.StatusInternalServerError, err) 137 137 return 138 138 } 139 - defer s.Close() 140 139 141 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 142 - Filename: pf.Name, 143 - LastModified: pf.CreatedUnix.AsLocalTime(), 144 - }) 140 + helper.ServePackageFile(ctx, s, u, pf) 145 141 } 146 142 147 143 // UploadPackage creates a new package
+26
routers/api/packages/helper/helper.go
··· 5 5 6 6 import ( 7 7 "fmt" 8 + "io" 8 9 "net/http" 10 + "net/url" 9 11 12 + packages_model "code.gitea.io/gitea/models/packages" 10 13 "code.gitea.io/gitea/modules/context" 11 14 "code.gitea.io/gitea/modules/log" 12 15 "code.gitea.io/gitea/modules/setting" ··· 35 38 cb(message) 36 39 } 37 40 } 41 + 42 + // Serves the content of the package file 43 + // If the url is set it will redirect the request, otherwise the content is copied to the response. 44 + func ServePackageFile(ctx *context.Context, s io.ReadSeekCloser, u *url.URL, pf *packages_model.PackageFile, forceOpts ...*context.ServeHeaderOptions) { 45 + if u != nil { 46 + ctx.Redirect(u.String()) 47 + return 48 + } 49 + 50 + defer s.Close() 51 + 52 + var opts *context.ServeHeaderOptions 53 + if len(forceOpts) > 0 { 54 + opts = forceOpts[0] 55 + } else { 56 + opts = &context.ServeHeaderOptions{ 57 + Filename: pf.Name, 58 + LastModified: pf.CreatedUnix.AsLocalTime(), 59 + } 60 + } 61 + 62 + ctx.ServeContent(s, opts) 63 + }
+3 -9
routers/api/packages/maven/maven.go
··· 210 210 return 211 211 } 212 212 213 - s, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(pb.HashSHA256)) 213 + s, u, _, err := packages_service.GetPackageBlobStream(ctx, pf, pb) 214 214 if err != nil { 215 215 apiError(ctx, http.StatusInternalServerError, err) 216 - } 217 - defer s.Close() 218 - 219 - if pf.IsLead { 220 - if err := packages_model.IncrementDownloadCounter(ctx, pv.ID); err != nil { 221 - log.Error("Error incrementing download counter: %v", err) 222 - } 216 + return 223 217 } 224 218 225 219 opts.Filename = pf.Name 226 220 227 - ctx.ServeContent(s, opts) 221 + helper.ServePackageFile(ctx, s, u, pf, opts) 228 222 } 229 223 230 224 // UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
+4 -12
routers/api/packages/npm/npm.go
··· 83 83 packageVersion := ctx.Params("version") 84 84 filename := ctx.Params("filename") 85 85 86 - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 86 + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 87 87 ctx, 88 88 &packages_service.PackageInfo{ 89 89 Owner: ctx.Package.Owner, ··· 103 103 apiError(ctx, http.StatusInternalServerError, err) 104 104 return 105 105 } 106 - defer s.Close() 107 106 108 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 109 - Filename: pf.Name, 110 - LastModified: pf.CreatedUnix.AsLocalTime(), 111 - }) 107 + helper.ServePackageFile(ctx, s, u, pf) 112 108 } 113 109 114 110 // DownloadPackageFileByName finds the version and serves the contents of a package ··· 134 130 return 135 131 } 136 132 137 - s, pf, err := packages_service.GetFileStreamByPackageVersion( 133 + s, u, pf, err := packages_service.GetFileStreamByPackageVersion( 138 134 ctx, 139 135 pvs[0], 140 136 &packages_service.PackageFileInfo{ ··· 149 145 apiError(ctx, http.StatusInternalServerError, err) 150 146 return 151 147 } 152 - defer s.Close() 153 148 154 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 155 - Filename: pf.Name, 156 - LastModified: pf.CreatedUnix.AsLocalTime(), 157 - }) 149 + helper.ServePackageFile(ctx, s, u, pf) 158 150 } 159 151 160 152 // UploadPackage creates a new package
+4 -12
routers/api/packages/nuget/nuget.go
··· 362 362 packageVersion := ctx.Params("version") 363 363 filename := ctx.Params("filename") 364 364 365 - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 365 + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 366 366 ctx, 367 367 &packages_service.PackageInfo{ 368 368 Owner: ctx.Package.Owner, ··· 382 382 apiError(ctx, http.StatusInternalServerError, err) 383 383 return 384 384 } 385 - defer s.Close() 386 385 387 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 388 - Filename: pf.Name, 389 - LastModified: pf.CreatedUnix.AsLocalTime(), 390 - }) 386 + helper.ServePackageFile(ctx, s, u, pf) 391 387 } 392 388 393 389 // UploadPackage creates a new package with the metadata contained in the uploaded nupgk file ··· 600 596 return 601 597 } 602 598 603 - s, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) 599 + s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) 604 600 if err != nil { 605 601 if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { 606 602 apiError(ctx, http.StatusNotFound, err) ··· 609 605 apiError(ctx, http.StatusInternalServerError, err) 610 606 return 611 607 } 612 - defer s.Close() 613 608 614 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 615 - Filename: pf.Name, 616 - LastModified: pf.CreatedUnix.AsLocalTime(), 617 - }) 609 + helper.ServePackageFile(ctx, s, u, pf) 618 610 } 619 611 620 612 // DeletePackage hard deletes the package
+2 -6
routers/api/packages/pub/pub.go
··· 273 273 274 274 pf := pd.Files[0].File 275 275 276 - s, _, err := packages_service.GetPackageFileStream(ctx, pf) 276 + s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) 277 277 if err != nil { 278 278 apiError(ctx, http.StatusInternalServerError, err) 279 279 return 280 280 } 281 - defer s.Close() 282 281 283 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 284 - Filename: pf.Name, 285 - LastModified: pf.CreatedUnix.AsLocalTime(), 286 - }) 282 + helper.ServePackageFile(ctx, s, u, pf) 287 283 }
+2 -6
routers/api/packages/pypi/pypi.go
··· 80 80 packageVersion := ctx.Params("version") 81 81 filename := ctx.Params("filename") 82 82 83 - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 83 + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 84 84 ctx, 85 85 &packages_service.PackageInfo{ 86 86 Owner: ctx.Package.Owner, ··· 100 100 apiError(ctx, http.StatusInternalServerError, err) 101 101 return 102 102 } 103 - defer s.Close() 104 103 105 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 106 - Filename: pf.Name, 107 - LastModified: pf.CreatedUnix.AsLocalTime(), 108 - }) 104 + helper.ServePackageFile(ctx, s, u, pf) 109 105 } 110 106 111 107 // UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
+4 -13
routers/api/packages/rpm/rpm.go
··· 65 65 return 66 66 } 67 67 68 - s, pf, err := packages_service.GetFileStreamByPackageVersion( 68 + s, u, pf, err := packages_service.GetFileStreamByPackageVersion( 69 69 ctx, 70 70 pv, 71 71 &packages_service.PackageFileInfo{ ··· 80 80 } 81 81 return 82 82 } 83 - defer s.Close() 84 83 85 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 86 - Filename: pf.Name, 87 - LastModified: pf.CreatedUnix.AsLocalTime(), 88 - }) 84 + helper.ServePackageFile(ctx, s, u, pf) 89 85 } 90 86 91 87 func UploadPackageFile(ctx *context.Context) { ··· 173 169 name := ctx.Params("name") 174 170 version := ctx.Params("version") 175 171 176 - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 172 + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 177 173 ctx, 178 174 &packages_service.PackageInfo{ 179 175 Owner: ctx.Package.Owner, ··· 193 189 } 194 190 return 195 191 } 196 - defer s.Close() 197 192 198 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 199 - ContentType: "application/x-rpm", 200 - Filename: pf.Name, 201 - LastModified: pf.CreatedUnix.AsLocalTime(), 202 - }) 193 + helper.ServePackageFile(ctx, s, u, pf) 203 194 } 204 195 205 196 func DeletePackageFile(webctx *context.Context) {
+2 -6
routers/api/packages/rubygems/rubygems.go
··· 175 175 return 176 176 } 177 177 178 - s, pf, err := packages_service.GetFileStreamByPackageVersion( 178 + s, u, pf, err := packages_service.GetFileStreamByPackageVersion( 179 179 ctx, 180 180 pvs[0], 181 181 &packages_service.PackageFileInfo{ ··· 190 190 apiError(ctx, http.StatusInternalServerError, err) 191 191 return 192 192 } 193 - defer s.Close() 194 193 195 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 196 - Filename: pf.Name, 197 - LastModified: pf.CreatedUnix.AsLocalTime(), 198 - }) 194 + helper.ServePackageFile(ctx, s, u, pf) 199 195 } 200 196 201 197 // UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
+2 -3
routers/api/packages/swift/swift.go
··· 397 397 398 398 pf := pd.Files[0].File 399 399 400 - s, _, err := packages_service.GetPackageFileStream(ctx, pf) 400 + s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) 401 401 if err != nil { 402 402 apiError(ctx, http.StatusInternalServerError, err) 403 403 return 404 404 } 405 - defer s.Close() 406 405 407 406 setResponseHeaders(ctx.Resp, &headers{ 408 407 Digest: pd.Files[0].Blob.HashSHA256, 409 408 }) 410 409 411 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 410 + helper.ServePackageFile(ctx, s, u, pf, &context.ServeHeaderOptions{ 412 411 Filename: pf.Name, 413 412 ContentType: "application/zip", 414 413 LastModified: pf.CreatedUnix.AsLocalTime(),
+2 -6
routers/api/packages/vagrant/vagrant.go
··· 216 216 } 217 217 218 218 func DownloadPackageFile(ctx *context.Context) { 219 - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 219 + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 220 220 ctx, 221 221 &packages_service.PackageInfo{ 222 222 Owner: ctx.Package.Owner, ··· 236 236 apiError(ctx, http.StatusInternalServerError, err) 237 237 return 238 238 } 239 - defer s.Close() 240 239 241 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 242 - Filename: pf.Name, 243 - LastModified: pf.CreatedUnix.AsLocalTime(), 244 - }) 240 + helper.ServePackageFile(ctx, s, u, pf) 245 241 }
+3 -9
routers/web/user/package.go
··· 22 22 "code.gitea.io/gitea/modules/setting" 23 23 "code.gitea.io/gitea/modules/util" 24 24 "code.gitea.io/gitea/modules/web" 25 + packages_helper "code.gitea.io/gitea/routers/api/packages/helper" 25 26 shared_user "code.gitea.io/gitea/routers/web/shared/user" 26 27 "code.gitea.io/gitea/services/forms" 27 28 packages_service "code.gitea.io/gitea/services/packages" ··· 443 444 return 444 445 } 445 446 446 - s, _, err := packages_service.GetPackageFileStream( 447 - ctx, 448 - pf, 449 - ) 447 + s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) 450 448 if err != nil { 451 449 ctx.ServerError("GetPackageFileStream", err) 452 450 return 453 451 } 454 - defer s.Close() 455 452 456 - ctx.ServeContent(s, &context.ServeHeaderOptions{ 457 - Filename: pf.Name, 458 - LastModified: pf.CreatedUnix.AsLocalTime(), 459 - }) 453 + packages_helper.ServePackageFile(ctx, s, u, pf) 460 454 }
+34 -40
services/packages/packages.go
··· 9 9 "errors" 10 10 "fmt" 11 11 "io" 12 + "net/url" 12 13 "strings" 13 14 14 15 "code.gitea.io/gitea/models/db" ··· 20 21 "code.gitea.io/gitea/modules/notification" 21 22 packages_module "code.gitea.io/gitea/modules/packages" 22 23 "code.gitea.io/gitea/modules/setting" 24 + "code.gitea.io/gitea/modules/storage" 23 25 "code.gitea.io/gitea/modules/util" 24 26 ) 25 27 ··· 562 564 } 563 565 564 566 // GetFileStreamByPackageNameAndVersion returns the content of the specific package file 565 - func GetFileStreamByPackageNameAndVersion(ctx context.Context, pvi *PackageInfo, pfi *PackageFileInfo) (io.ReadSeekCloser, *packages_model.PackageFile, error) { 567 + func GetFileStreamByPackageNameAndVersion(ctx context.Context, pvi *PackageInfo, pfi *PackageFileInfo) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { 566 568 log.Trace("Getting package file stream: %v, %v, %s, %s, %s, %s", pvi.Owner.ID, pvi.PackageType, pvi.Name, pvi.Version, pfi.Filename, pfi.CompositeKey) 567 569 568 570 pv, err := packages_model.GetVersionByNameAndVersion(ctx, pvi.Owner.ID, pvi.PackageType, pvi.Name, pvi.Version) 569 571 if err != nil { 570 572 if err == packages_model.ErrPackageNotExist { 571 - return nil, nil, err 573 + return nil, nil, nil, err 572 574 } 573 575 log.Error("Error getting package: %v", err) 574 - return nil, nil, err 576 + return nil, nil, nil, err 575 577 } 576 578 577 579 return GetFileStreamByPackageVersion(ctx, pv, pfi) 578 580 } 579 581 580 - // GetFileStreamByPackageVersionAndFileID returns the content of the specific package file 581 - func GetFileStreamByPackageVersionAndFileID(ctx context.Context, owner *user_model.User, versionID, fileID int64) (io.ReadSeekCloser, *packages_model.PackageFile, error) { 582 - log.Trace("Getting package file stream: %v, %v, %v", owner.ID, versionID, fileID) 583 - 584 - pv, err := packages_model.GetVersionByID(ctx, versionID) 585 - if err != nil { 586 - if err != packages_model.ErrPackageNotExist { 587 - log.Error("Error getting package version: %v", err) 588 - } 589 - return nil, nil, err 590 - } 591 - 592 - p, err := packages_model.GetPackageByID(ctx, pv.PackageID) 593 - if err != nil { 594 - log.Error("Error getting package: %v", err) 595 - return nil, nil, err 596 - } 597 - 598 - if p.OwnerID != owner.ID { 599 - return nil, nil, packages_model.ErrPackageNotExist 600 - } 601 - 602 - pf, err := packages_model.GetFileForVersionByID(ctx, versionID, fileID) 603 - if err != nil { 604 - log.Error("Error getting file: %v", err) 605 - return nil, nil, err 606 - } 607 - 608 - return GetPackageFileStream(ctx, pf) 609 - } 610 - 611 582 // GetFileStreamByPackageVersion returns the content of the specific package file 612 - func GetFileStreamByPackageVersion(ctx context.Context, pv *packages_model.PackageVersion, pfi *PackageFileInfo) (io.ReadSeekCloser, *packages_model.PackageFile, error) { 583 + func GetFileStreamByPackageVersion(ctx context.Context, pv *packages_model.PackageVersion, pfi *PackageFileInfo) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { 613 584 pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, pfi.Filename, pfi.CompositeKey) 614 585 if err != nil { 615 - return nil, nil, err 586 + return nil, nil, nil, err 616 587 } 617 588 618 589 return GetPackageFileStream(ctx, pf) 619 590 } 620 591 621 592 // GetPackageFileStream returns the content of the specific package file 622 - func GetPackageFileStream(ctx context.Context, pf *packages_model.PackageFile) (io.ReadSeekCloser, *packages_model.PackageFile, error) { 593 + func GetPackageFileStream(ctx context.Context, pf *packages_model.PackageFile) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { 623 594 pb, err := packages_model.GetBlobByID(ctx, pf.BlobID) 624 595 if err != nil { 625 - return nil, nil, err 596 + return nil, nil, nil, err 626 597 } 627 598 628 - s, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(pb.HashSHA256)) 599 + return GetPackageBlobStream(ctx, pf, pb) 600 + } 601 + 602 + // GetPackageBlobStream returns the content of the specific package blob 603 + // If the storage supports direct serving and it's enabled, only the direct serving url is returned. 604 + func GetPackageBlobStream(ctx context.Context, pf *packages_model.PackageFile, pb *packages_model.PackageBlob) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { 605 + key := packages_module.BlobHash256Key(pb.HashSHA256) 606 + 607 + cs := packages_module.NewContentStore() 608 + 609 + var s io.ReadSeekCloser 610 + var u *url.URL 611 + var err error 612 + 613 + if cs.ShouldServeDirect() { 614 + u, err = cs.GetServeDirectURL(key, pf.Name) 615 + if err != nil && !errors.Is(err, storage.ErrURLNotSupported) { 616 + log.Error("Error getting serve direct url: %v", err) 617 + } 618 + } 619 + if u == nil { 620 + s, err = cs.Get(key) 621 + } 622 + 629 623 if err == nil { 630 624 if pf.IsLead { 631 625 if err := packages_model.IncrementDownloadCounter(ctx, pf.VersionID); err != nil { ··· 633 627 } 634 628 } 635 629 } 636 - return s, pf, err 630 + return s, u, pf, err 637 631 } 638 632 639 633 // RemoveAllPackages for User
+37
tests/integration/api_packages_generic_test.go
··· 6 6 import ( 7 7 "bytes" 8 8 "fmt" 9 + "io" 9 10 "net/http" 10 11 "testing" 11 12 ··· 138 139 139 140 req = NewRequest(t, "GET", url+"/dummy.bin") 140 141 MakeRequest(t, req, http.StatusUnauthorized) 142 + }) 143 + 144 + t.Run("ServeDirect", func(t *testing.T) { 145 + defer tests.PrintCurrentTest(t)() 146 + 147 + if setting.Packages.Storage.Type != setting.MinioStorageType { 148 + t.Skip("Test skipped for non-Minio-storage.") 149 + return 150 + } 151 + 152 + if !setting.Packages.Storage.MinioConfig.ServeDirect { 153 + old := setting.Packages.Storage.MinioConfig.ServeDirect 154 + defer func() { 155 + setting.Packages.Storage.MinioConfig.ServeDirect = old 156 + }() 157 + 158 + setting.Packages.Storage.MinioConfig.ServeDirect = true 159 + } 160 + 161 + req := NewRequest(t, "GET", url+"/"+filename) 162 + resp := MakeRequest(t, req, http.StatusSeeOther) 163 + 164 + checkDownloadCount(3) 165 + 166 + location := resp.Header().Get("Location") 167 + assert.NotEmpty(t, location) 168 + 169 + resp2, err := (&http.Client{}).Get(location) 170 + assert.NoError(t, err) 171 + assert.Equal(t, http.StatusOK, resp2.StatusCode) 172 + 173 + body, err := io.ReadAll(resp2.Body) 174 + assert.NoError(t, err) 175 + assert.Equal(t, content, body) 176 + 177 + checkDownloadCount(3) 141 178 }) 142 179 }) 143 180