![]() |
VOOZH | about |
dotnet add package CosmoS3 --version 2.3.5
NuGet\Install-Package CosmoS3 -Version 2.3.5
<PackageReference Include="CosmoS3" Version="2.3.5" />
<PackageVersion Include="CosmoS3" Version="2.3.5" />Directory.Packages.props
<PackageReference Include="CosmoS3" />Project file
paket add CosmoS3 --version 2.3.5
#r "nuget: CosmoS3, 2.3.5"
#:package CosmoS3@2.3.5
#addin nuget:?package=CosmoS3&version=2.3.5Install as a Cake Addin
#tool nuget:?package=CosmoS3&version=2.3.5Install as a Cake Tool
CosmoS3 is an Amazon S3βcompatible object-storage middleware library for . It implements core S3 operations with a pluggable metadata store β SQL Server, PostgreSQL, MySQL, SQLite, the embedded CosmoKv SQL engine, or kvdirect (a no-SQL ordered-KV index for single-node deployments) β and the local disk (or a pluggable storage driver) for object data. Full-object GETs stream diskβsocket through a kernel zero-copy (sendfile) path.
It can be consumed three ways: as a standalone S3 endpoint (CosmoS3.Host), as middleware inside an existing CosmoApiServer app, or embedded in-process via the IS3Repository data layer.
On a Linux NVMe server, v2.3.0 beats native-config MinIO on every operation measured β GET 2β3Γ (up to ~9.85 GiB/s), LIST 2.1Γ, HEAD 1.47Γ β see Performance.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CosmoApiServer β
β (System.IO.Pipelines transport, HTTP/1.1Β·2Β·3; β
β kernel sendfile zero-copy GET on plaintext HTTP/1) β
ββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
β IMiddleware
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β S3Middleware β
β β’ S3Context.CreateAsync β async, non-blocking parse β
β β’ Authenticates (SigV4 / SigV2 / presigned) β
β β’ Verifies the SigV4 signature (ValidateSignatures) β
β β’ Routes to ServiceHandler / BucketHandler / β
β ObjectHandler / Admin + internal handlers β
ββββββββββββββββββ¬ββββββββββββββββββββββββ¬βββββββββββββββ
β β
βββββββββββββΌββββββββββ βββββββββββββΌβββββββββββββ
β DataAccess β β Storage Driver β
β β IS3Repository: β β (DiskStorageDriver: β
β β’ S3Repository β β atomic write + move, β
β (5 SQL backends) β β ArrayPool, SubStream, β
β β’ KvDirectRepo β β zero-copy GET path) β
β (ordered KV) β β β
ββββββββββββββββββββββββ βββββββββββββββββββββββββββ
Key types:
| Type | Role |
|---|---|
S3Middleware |
Entry point; implements IMiddleware. Builds the request via S3Context.CreateAsync (fully async β no sync-over-async body buffering) and enforces SigV4 when ValidateSignatures is on |
S3Request / S3Response / S3Context |
Async request parse, S3-formatted response writer, combined handler context |
AuthManager |
Resolves the credential/user and computes the authorization decision (SigV2 / SigV4 / presigned) |
SignatureV4Verifier / Helpers/SignatureV4 |
Recomputes the AWS SigV4 canonical request and compares signatures in constant time |
BucketManager |
Lock-free in-memory bucket registry (ConcurrentDictionary); lazily populated from the DB on a miss |
ConfigManager |
User / credential / bucket / object lookup (DB or seeded no-DB mode) |
DataAccess β IS3Repository |
Static facade over the metadata layer. Two implementations behind one contract: S3Repository (parameterized SQL via CosmoSQLClient, portable across all five SQL backends) and KvDirectRepository (metadata directly on the CosmoKv ordered-KV engine β no SQL) |
KvDirectRepository |
DatabaseType=kvdirect β all metadata in one byte-ordered KV keyspace; latest-version lookup is a single seek and listing is a native cursor. Single-node embedded backend |
DiskStorageDriver |
Streaming I/O using ArrayPool<byte> and SubStream; writes to a temp sibling then File.Move for atomic, durable publish. Full-object GETs use the framework's kernel sendfile path |
The design choices below are deliberate; each closes a concrete failure mode. They are enforced by the unit + integration test suite (see Testing).
| Area | Behavior | Why |
|---|---|---|
| SigV4 verification | When ValidateSignatures is true (default) the server recomputes the canonical request and compares signatures with CryptographicOperations.FixedTimeEquals, plus a Β±15-minute clock-skew check (RequestTimeTooSkewed). Validated against AWS's official get-vanilla test vector. |
Authentication that parses a signature but never checks it is no authentication. Constant-time comparison avoids leaking the secret via timing. |
| No default admin key | AdminApiKey defaults to ""; an empty configured key never matches any presented key (SecurityHelper.ConfiguredKeyMatches). |
A shipped default master key (the old "cosmos3admin") is a backdoor. Operators must set one explicitly. |
| Real principal on writes | Object writes are credited to the authenticated user. Anonymous / public-write requests fall back to the configurable AnonymousOwnerGuid; if that is unset the write is rejected with AccessDenied. |
The former 0.0.0.0:0 sentinel owner resolved to no user, which made GET ?acl return 500. Owners must reference a real principal. |
| Bucket-name validation | SecurityHelper.IsValidBucketName rejects path-traversal and out-of-spec names before they reach the filesystem. |
Bucket names become directory names; ../ must never escape the storage root. |
| Atomic, durable objects | The disk driver writes to a unique temp sibling, flush(true), then File.Move to publish; a short read against the declared length throws instead of publishing a truncated object. |
A crash mid-write must never leave a half-written object visible. |
| Versioning write race | (bucketguid, objectkey, version) is a UNIQUE index; concurrent writers that collide on a version retry with the next number. |
Two simultaneous PUTs to the same key must not silently produce duplicate or lost versions. |
| Conditional requests | If-Match / If-None-Match / If-[Un]Modified-Since are honored on GET/HEAD (304 / 412) and PUT (If-None-Match: * overwrite guard, If-Match optimistic concurrency) per RFC 7232. |
Lets clients do safe caching and lost-update-free updates. |
| Bounded-memory listing | ListObjects(V2) streams rows and stops at the page boundary, then hydrates only that page's keys β it never loads a whole bucket into memory. |
A bucket with millions of keys must not OOM the server on a single ls. |
| Single-pass multipart | CompleteMultipartUpload streams the part files straight into the destination blob (ConcatReadStream) instead of concatenating to a temp file and re-copying. |
Avoids writing every byte twice and the extra temp-space high-water mark on large objects. |
CosmoS3 is optimized for high-throughput, low-latency workloads. With the kvdirect embedded backend and
a kernel zero-copy GET path, v2.3.0 beats native-config MinIO on every operation on a Linux NVMe server β
decisively on reads (GET up to ~9.85 GiB/s) and listing.
CosmoS3 v2.3.0 (kvdirect backend) vs MinIO on a 12-core x86_64 NVMe server (the deployment
target). Both Dockerized with host networking on a single local disk; warp S3 benchmark, 1 MiB / 4 MiB
objects, 15 s, zero errors either side. MinIO runs single-drive (no erasure coding β its fastest
config, the hardest baseline). Sequential runs.
| Operation | Concurrency | CosmoS3 v2.3.0 | MinIO | CosmoS3 vs MinIO |
|---|---|---|---|---|
| PUT 1 MiB | 16 | 333 obj/s | 281 | +19% |
| GET 1 MiB | 8 | 6,109 obj/s (6.1 GiB/s) | 2,510 | +143% (2.4Γ) |
| GET 1 MiB | 16 | 5,825 obj/s | 2,863 | +103% (2.0Γ) |
| GET 4 MiB | 16 | 9,850 MiB/s (2,463 obj/s) | 3,328 MiB/s | +196% (3.0Γ) |
| HEAD | 16 | 26,350 obj/s | 17,919 | +47% |
| LIST | 16 | 126,245 obj/s | 59,511 | +112% (2.1Γ) |
CosmoS3 wins every operation measured. GET is the standout β the kernel sendfile path streams
full-object bodies diskβsocket with no user-space copy, sustaining up to 9.85 GiB/s. PUT is CPU-bound
and was measured on a shared host, so treat its margin as parity-to-favourable; the GET/LIST/HEAD wins
(sendfile + ordered-KV, not CPU-bound the same way) are large and robust.
Key Optimizations:
sendfile(2)/TransmitFile β no user-space copy or read loop; objects β€ 256 KiB serve from an in-memory file cache. (Ranges / TLS / HTTP-2/3 fall back to buffered streaming.)kvdirect ordered-KV metadata: latest-version lookup is a single seek and listing is a native byte-ordered cursor (no SQL parse/plan), driving the LIST 2.1Γ and HEAD 1.47Γ wins. See the DatabaseSettings section.MetadataCacheSeconds) over credentials / bucket-ACL / object metadata so an authenticated GET/HEAD can hit zero metadata round-trips.ArrayPool<byte> across all read/write paths to eliminate transient allocations and GC pressure.ConcurrentDictionary bucket registry for non-blocking lookups under high concurrency.sendfile β GET up to ~9.85 GiB/s, ~2β3Γ native MinIO on Linux NVMe. (Requires CosmoApiServer.Core β₯ 3.8.0.)kvdirect embedded backend (v2.2.0): an IS3Repository directly on the CosmoKv ordered-KV engine β no SQL β for single-node deployments; listing 2.1Γ and HEAD 1.47Γ vs MinIO.If-Match / If-None-Match / If-[Un]Modified-Since on GET/HEAD/PUT (304 / 412, overwrite guard, optimistic concurrency).(bucket, key, version) index and collision-retry assignment; GET/PUT ?versioning, ListObjectVersions, and x-amz-version-id.ListObjects(V2) streams and pages at the database level instead of loading the whole bucket.IS3Repository.S3Context.CreateAsync removed the sync-over-async body buffering (and the thread-pool workaround it required).aws-chunked streaming decode, and a benchmarking suite for comparison against any S3-compatible backend.A ready-to-run host is included at samples/CosmoS3Host/, with backend-specific variants and demos alongside it:
cd samples/CosmoS3Host # default
dotnet run
| Sample | Purpose |
|---|---|
CosmoS3Host |
Default standalone S3 endpoint |
CosmoS3Host.SqlServer / .Postgres / .MySQL / .SQLite |
Same host wired to each SQL backend |
InternalApiDemo |
Using the lightweight internal REST API (InternalApiKey) |
CosmoBroker.AuthDemo |
Authentication / credential wiring |
using CosmoS3;
using CosmoS3.Settings;
var settings = new SettingsBase
{
RegionString = "us-east-1",
ValidateSignatures = false, // set true in production
Storage = new StorageSettings
{
StorageType = CosmoS3.Storage.StorageDriverType.Disk,
DiskDirectory = "./data/objects"
},
Database = new DatabaseSettings
{
Hostname = "localhost",
Port = 1433,
DatabaseName = "MyDatabase",
Username = "sa",
Password = "your-password"
},
// Optional: enable CORS for browser-based S3 clients
Cors = new CorsSettings { Enabled = true },
// Optional: enable HTTPS
// CertificatePath = "./certs/server.pfx",
// CertificatePassword = "changeme",
// Optional: enable HTTP/2 cleartext (h2c)
// EnableHttp2 = true,
};
// CosmoS3Application.Create() wires TLS, HTTP/2, CORS, logging, and S3Middleware.
var app = CosmoS3Application.Create(settings, port: 8100);
app.Run();
Or wire manually for full control over the middleware order:
using CosmoApiServer.Core.Hosting;
using CosmoS3;
using CosmoS3.Settings;
var app = CosmoWebApplicationBuilder.Create()
.ListenOn(8100)
.UseHttps("./certs/server.pfx", "changeme") // optional TLS
.UseHttp3() // optional HTTP/3 over QUIC
.UseHttp2() // optional h2c
.UseCors() // optional CORS
.UseLogging()
.UseMiddleware(new S3Middleware(settings))
.Build();
app.Run();
SettingsBase| Property | Type | Default | Description |
|---|---|---|---|
ValidateSignatures |
bool |
true |
Verify AWS SigV4/V2 on every request. When on, signatures are recomputed and checked (not just parsed). Disable for local dev only. |
BaseDomain |
string? |
null |
Set to enable virtual-hostedβstyle URLs (e.g. "localhost"). Leave null for path-style. |
RegionString |
string |
"us-west-1" |
AWS region identifier returned in responses. |
HeaderApiKey |
string |
"x-api-key" |
HTTP header name for admin API authentication. |
AdminApiKey |
string |
"" |
Secret value expected in HeaderApiKey for admin endpoints. Empty by default β an empty key never matches; set one to enable the admin API. |
InternalApiKey |
string? |
null |
When set, enables the lightweight internal REST API at /internal/ for requests carrying Authorization: Bearer {InternalApiKey}. Null disables it. |
AnonymousOwnerGuid |
string |
"" |
Principal credited as owner when an anonymous / public-write request creates an object. Empty rejects anonymous writes (AccessDenied); set it to a real user GUID to allow them. |
MetadataCacheSeconds |
double |
0 |
Opt-in TTL (seconds) for the in-process metadata fast-path (credentials, bucket ACL, object metadata, latest-version). 0 disables it. Keep small (1β5 s): a revoked credential can still authenticate for up to the TTL on a node that didn't process the revocation. |
Database |
DatabaseSettings |
(required) | Metadata-store connection (any of the six backends). |
Storage |
StorageSettings |
(required) | Object storage configuration. |
Logging |
LoggingSettings |
default | Log level callbacks. |
Debug |
DebugSettings |
default | Enable extra debug output. |
Users / Credentials / Buckets |
List<T> |
empty | Seed in-memory data for no-database mode (testing). |
CertificatePath |
string? |
null |
Path to PFX file for HTTPS. When set, TLS is automatically applied. |
CertificatePassword |
string? |
null |
Password for the PFX certificate. |
EnableHttp2 |
bool |
false |
Enable h2c (HTTP/2 cleartext) support. |
EnableHttp3 |
bool |
false |
Enable HTTP/3 over QUIC on the TLS listener. Requires CertificatePath. |
Cors |
CorsSettings |
disabled | CORS configuration for browser-based S3 clients. |
DatabaseSettingsCosmoS3 selects the backend from DatabaseType; the schema is created/updated at startup via DatabaseFactory.EnsureSchemaAsync.
| Property | Default | Description |
|---|---|---|
DatabaseType |
"mssql" |
One of mssql, postgres, mysql, sqlite, cosmokv, kvdirect. |
ConnectionString |
null |
Full connection string. When set, used verbatim (required for sqlite/cosmokv/kvdirect, e.g. Data Source=...). |
Hostname / Port / Username / Password / DatabaseName / Instance |
β | Convenience fields for the server-based engines; used to build a connection string when ConnectionString is not given. |
For SQL Server the built connection string is:
server=HOSTNAME,PORT;database=DBNAME;user id=USER;password=PASS;TrustServerCertificate=true;
SQLite and the embedded CosmoKv engine need no server β point ConnectionString at a file/data-source path.
kvdirect β embedded ordered-KV metadata (no SQL)kvdirect stores all metadata directly on the CosmoKv ordered-KV engine β the same LSM store the
cosmokv backend uses, but without the SQL parser/planner/connection-pool on top. Object keys are
byte-ordered (<bucket> 0x00 <key> 0x00 <invVersion>) so the latest version is a single seek and a
bucket listing is a native cursor; there is no schema (the default user/credential/bucket/owner-ACL are
seeded automatically on first open). Point ConnectionString at a directory:
"Database": { "DatabaseType": "kvdirect", "ConnectionString": "Data Source=./cosmos3-kvdirect" }
It is single-process / single-node by design (one embedded store, no shared DB) β the MinIO-style
"just run the binary" deployment. Use a SQL backend (postgres/mssql/mysql) when multiple nodes must
share one metadata store. End-to-end it runs ~6.3Γ faster across PUT/GET/HEAD/DELETE than the same
engine via SQL; see .
StorageSettings| Property | Default | Description |
|---|---|---|
StorageType |
Disk |
Disk is the only currently supported driver |
DiskDirectory |
"./disk/" |
Root directory for object files (no trailing slash) |
TempDirectory |
"./temp/" |
Scratch directory for multipart upload assembly |
CosmoS3 supports multiple database engines including SQL Server, PostgreSQL, MySQL, and SQLite. The schema is automatically created or updated at startup via DatabaseFactory.EnsureSchemaAsync.
The
kvdirectbackend has no SQL schema β the tables below map to a single byte-ordered key namespace on the CosmoKv engine (object rows ato\0<bucket>\0<key>\0<invVersion>, with secondary indexes for name / access-key / object-GUID lookups), and the default user/credential/bucket/owner-ACL are seeded on first open. Seekvdirect.
| Table | Purpose |
|---|---|
s3_users |
S3 user accounts |
s3_credentials |
Access key / secret key pairs linked to users |
s3_buckets |
Bucket metadata (name, owner, region, storage config) |
s3_objects |
Object metadata (key, size, ETag, version, blob reference) |
s3_objecttags |
Per-object tags |
s3_buckettags |
Per-bucket tags |
s3_uploads |
Active multipart upload sessions |
s3_uploadparts |
Uploaded parts for active sessions |
The schema includes composite indexes that stay fast with millions of objects and also enforce integrity:
ux_s3_objects_bucket_key_version: UNIQUE (bucketguid, objectkey, version DESC) β serves the common "get latest version" lookup and makes the versioning write race impossible at the storage layer (collisions retry with the next version). On CosmoKv the DESC qualifier is dropped (engine limitation) but the uniqueness constraint is identical.idx_s3_objects_guid: (guid) β blob/version resolution by object GUID.idx_s3_credentials_accesskey: (accesskey) β rapid authentication.idx_s3_buckets_name: (name) β fast bucket resolution.All database operations go through the static DataAccess facade over the backend-portable IS3Repository. The SQL backends use S3Repository (parameterized SQL β no stored procedures, so the same code runs unchanged on every engine); kvdirect uses KvDirectRepository, which implements the identical contract directly on ordered-KV primitives. The versioning write race is prevented on both: a UNIQUE (bucket, key, version) index on SQL, and optimistic-commit conflict retry on kvdirect.
| Operation | AWS CLI command | Status |
|---|---|---|
| List Buckets | aws s3 ls |
β |
| Operation | AWS CLI command | Status |
|---|---|---|
| Create Bucket | aws s3 mb s3://bucket |
β |
| Delete Bucket | aws s3 rb s3://bucket |
β |
| List Objects (v1 & v2) | aws s3 ls s3://bucket/ |
β |
| Get Bucket ACL | aws s3api get-bucket-acl |
β |
| Put Bucket ACL | aws s3api put-bucket-acl |
β |
| Get Bucket Tags | aws s3api get-bucket-tagging |
β |
| Put Bucket Tags | aws s3api put-bucket-tagging |
β |
| Delete Bucket Tags | aws s3api delete-bucket-tagging |
β |
| Get/Put/Delete Bucket Website | aws s3api *-bucket-website |
β |
| Get Bucket Location | aws s3api get-bucket-location |
β |
| Get Bucket Versioning | aws s3api get-bucket-versioning |
β |
| Operation | AWS CLI command | Status |
|---|---|---|
| Put Object | aws s3 cp local.txt s3://bucket/key |
β |
| Get Object | aws s3 cp s3://bucket/key local.txt |
β |
| Head Object | aws s3api head-object |
β |
| Delete Object | aws s3 rm s3://bucket/key |
β |
| Delete Objects (batch) | aws s3 sync --delete |
β |
| Copy Object | aws s3 cp s3://src s3://dst |
β |
| Get Object ACL | aws s3api get-object-acl |
β |
| Put Object ACL | aws s3api put-object-acl |
β |
| Get Object Tags | aws s3api get-object-tagging |
β |
| Put Object Tags | aws s3api put-object-tagging |
β |
| Delete Object Tags | aws s3api delete-object-tagging |
β |
| Presigned GET/PUT URLs | SDK GetPreSignedURL |
β |
| Multipart Upload | aws s3 cp (large files) |
β |
| List Multipart Uploads | aws s3api list-multipart-uploads |
β |
| Abort Multipart Upload | aws s3api abort-multipart-upload |
β |
| Conditional GET/HEAD/PUT | If-Match / If-None-Match / If-[Un]Modified-Since |
β |
| List Object Versions | aws s3api list-object-versions |
β |
ValidateSignatures enabled (default), SigV4 signatures are cryptographically verified, not merely parsed.304 Not Modified / 412 Precondition Failed; PUT supports If-None-Match: * (don't overwrite) and If-Match (optimistic concurrency) per RFC 7232.aws-chunked transfer encoding: Automatically decoded via the async body path; works with aws s3 cp for any file size.PUT ?versioning; when enabled, writes create new version rows, deletes write delete markers, and responses carry x-amz-version-id. List historic versions with list-object-versions.AnonymousOwnerGuid to a real user GUID to allow public-write buckets.CorsSettings, not per-bucket.)A bucket can be configured to serve static files over plain HTTP (no AWS credentials required).
# Create bucket
aws --endpoint-url http://localhost:8100 s3 mb s3://my-site
# Upload content
aws --endpoint-url http://localhost:8100 s3 cp index.html s3://my-site/index.html --content-type text/html
aws --endpoint-url http://localhost:8100 s3 cp error.html s3://my-site/error.html --content-type text/html
# Enable website hosting
aws --endpoint-url http://localhost:8100 s3 website s3://my-site \
--index-document index.html \
--error-document error.html
# Bucket root returns index.html
curl http://localhost:8100/my-site/
# Unknown path returns error.html with 404
curl http://localhost:8100/my-site/missing.html
aws --endpoint-url http://localhost:8100 s3api put-bucket-website \
--bucket my-site \
--website-configuration '{
"RedirectAllRequestsTo": { "HostName": "example.com", "Protocol": "https" }
}'
aws --endpoint-url http://localhost:8100 s3api put-bucket-website \
--bucket my-site \
--website-configuration '{
"IndexDocument": { "Suffix": "index.html" },
"ErrorDocument": { "Key": "error.html" },
"RoutingRules": [
{
"Condition": { "KeyPrefixEquals": "old/" },
"Redirect": { "ReplaceKeyPrefixWith": "new/" }
}
]
}'
How it works:
website.xml at <DiskDirectory>/<bucketName>/website.xml./, the index document is served.Presigned URLs grant time-limited access to an S3 object without requiring the caller to have AWS credentials.
var request = new GetPreSignedUrlRequest
{
BucketName = "my-bucket",
Key = "my-object.txt",
Expires = DateTime.UtcNow.AddMinutes(15),
Verb = HttpVerb.GET
};
string url = s3Client.GetPreSignedURL(request);
# Download with curl (no AWS credentials needed)
curl "<presigned-url>" -o downloaded.txt
# Upload with a presigned PUT URL
curl -X PUT "<presigned-put-url>" --data-binary @file.txt
Signature version behavior:
AWSSDK generates SigV2 presigned URLs for custom (non-AWS) endpoints. CosmoS3 validates both:
| Version | Query params |
|---|---|
| SigV2 | AWSAccessKeyId, Signature, Expires (Unix timestamp) |
| SigV4 | X-Amz-Credential, X-Amz-Signature, X-Amz-Expires |
Expired presigned URLs return HTTP 403 ExpiredToken.
Multipart upload allows large files to be uploaded in parts and assembled server-side.
# CosmoS3 handles chunked uploads transparently
aws --endpoint-url http://localhost:8100 \
s3 cp large-file.bin s3://my-bucket/large-file.bin \
--expected-size 1073741824 # hint for 1 GB file
// 1. Initiate upload
var initResponse = await s3.InitiateMultipartUploadAsync(new InitiateMultipartUploadRequest
{
BucketName = "my-bucket",
Key = "my-object"
});
string uploadId = initResponse.UploadId;
// 2. Upload parts (minimum 5 MB each, except the last)
var uploadPartResponse = await s3.UploadPartAsync(new UploadPartRequest
{
BucketName = "my-bucket",
Key = "my-object",
UploadId = uploadId,
PartNumber = 1,
InputStream = partStream,
PartSize = partStream.Length
});
// 3. Complete the upload
await s3.CompleteMultipartUploadAsync(new CompleteMultipartUploadRequest
{
BucketName = "my-bucket",
Key = "my-object",
UploadId = uploadId,
PartETags = new List<PartETag> { new PartETag(1, uploadPartResponse.ETag) }
});
Internals:
TempDirectory and hashed on arrival.CompleteMultipartUpload streams the ordered part files directly into the destination blob via ConcatReadStream (single pass β no concatenate-to-temp-then-recopy), then deletes the parts. The object length is the sum of the recorded part lengths.s3_uploads / s3_uploadparts and can be listed or aborted.aws configure
# AWS Access Key ID: default
# AWS Secret Access Key: default
# Default region name: us-east-1
# Default output format: json
ENDPOINT=http://localhost:8100
# List buckets
aws --endpoint-url $ENDPOINT s3 ls
# Create bucket
aws --endpoint-url $ENDPOINT s3 mb s3://my-bucket
# Upload file
aws --endpoint-url $ENDPOINT s3 cp file.txt s3://my-bucket/
# Download file
aws --endpoint-url $ENDPOINT s3 cp s3://my-bucket/file.txt ./
# List objects
aws --endpoint-url $ENDPOINT s3 ls s3://my-bucket/
# Sync directory
aws --endpoint-url $ENDPOINT s3 sync ./local-dir/ s3://my-bucket/prefix/
# Delete object
aws --endpoint-url $ENDPOINT s3 rm s3://my-bucket/file.txt
# Delete bucket (must be empty)
aws --endpoint-url $ENDPOINT s3 rb s3://my-bucket
# Tag a bucket
aws --endpoint-url $ENDPOINT s3api put-bucket-tagging \
--bucket my-bucket \
--tagging '{"TagSet":[{"Key":"env","Value":"dev"}]}'
# Get bucket tags
aws --endpoint-url $ENDPOINT s3api get-bucket-tagging --bucket my-bucket
# Get bucket website config
aws --endpoint-url $ENDPOINT s3api get-bucket-website --bucket my-bucket
# Presigned URL (60 seconds)
aws --endpoint-url $ENDPOINT s3 presign s3://my-bucket/file.txt --expires-in 60
The suite (tests/CosmoS3.Tests/) uses xUnit + AWSSDK.S3 and needs no external database or running server β it boots the real server in-process against an embedded CosmoKv DB.
Tests are split into two xUnit traits that must run as separate dotnet test invocations, because the server's data layer is a process-global singleton (DataAccess._repo), so only one server instance can exist per process:
# Server-free unit tests (signature math, helpers, conditional-header logic, ...)
dotnet test tests/CosmoS3.Tests --filter "Category=Unit"
# Integration tests β boot the in-process server, drive it through the AWS SDK
dotnet test tests/CosmoS3.Tests --filter "Category=Integration"
CI runs the two lanes as separate steps (.github/workflows/ci.yml). Current status: 62 unit + 12 integration, all passing.
CosmoServerFixture (integration) boots the server with CosmoWebApplication.RunAsync on an ephemeral port over a temp CosmoKv database, and hands tests a configured AmazonS3Client (NewS3Client()). Shared via the [Collection("CosmoServer")] xUnit collection.S3Fixture is the legacy fixture that targets an externally running endpoint, retained for the original end-to-end tests.| Area | Tests |
|---|---|
SigV4 signing math (official AWS get-vanilla vector) + live accept/reject |
SignatureV4Tests, SignatureV4IntegrationTests |
| Conditional headers (304 / 412, RFC 7232 precedence) | ConditionalHeadersTests, ConditionalHeadersIntegrationTests |
| Listing: pagination, delimiter common-prefixes, delete-marker hiding | ListingPagingIntegrationTests |
| Multipart ordered round-trip | MultipartIntegrationTests |
| ACL owner resolution after authenticated PUT (M2) | OwnerAclIntegrationTests |
| Security helpers, data integrity, streaming safety, bucket cache | SecurityHelperTests, DataIntegrityTests, StreamingSafetyTests, BucketCacheTests |
| Bucket / object / multipart / presigned / website end-to-end | BucketTests, ObjectTests, MultipartTests, PresignedUrlTests, WebsiteTests |
src/CosmoS3/
βββ S3Middleware.cs # IMiddleware entry point; async parse, SigV4 verify, routing
βββ S3Request.cs # HTTP β S3 request parsing (CreateAsync, aws-chunked decode)
βββ S3Response.cs # S3-formatted response writer
βββ S3Context.cs # Combined request + response context (CreateAsync factory)
βββ S3Exception.cs # Typed S3 error thrown by handlers
βββ SignatureV4Verifier.cs # Recompute + constant-time compare of the SigV4 signature
βββ DataAccess.cs # Static facade over IS3Repository
βββ IS3Repository.cs # Backend-portable data-layer contract
βββ S3Repository.cs # Parameterized-SQL implementation (all 5 SQL backends)
βββ KvDirectRepository.cs # Embedded ordered-KV implementation (no SQL β DatabaseType=kvdirect)
βββ DatabaseFactory.cs # Backend selection + schema bootstrap
βββ CosmoS3Application.cs # Turn-key host builder (TLS / HTTP2Β·3 / CORS / middleware)
βββ Settings/ # SettingsBase, Database/Storage/Logging/Debug/Cors settings
βββ Helpers/
β βββ SignatureV4.cs # SigV4 canonical-request + signing-key primitives
β βββ SecurityHelper.cs # FixedTimeEquals, ConfiguredKeyMatches, IsValidBucketName
β βββ ConditionalHeaders.cs# RFC 7232 If-Match / If-None-Match / If-[Un]Modified-Since
β βββ ConcatReadStream.cs # Single-pass multipart assembly stream
β βββ AclConverter.cs # ACL β policy conversion (grantees validated vs DB)
β βββ RequestValidator.cs # Shared handler precondition checks
β βββ HashHelper.cs # Single-pass MD5/SHA hashing
βββ Classes/
β βββ AuthManager.cs # Credential/user resolution + authorization decision
β βββ BucketManager.cs # Lock-free ConcurrentDictionary bucket registry
β βββ BucketClient.cs # Per-bucket storage driver accessor
β βββ ConfigManager.cs # User / credential / bucket / object lookup
β βββ CleanupManager.cs # Background task: expire stale temp files
βββ Api/S3/
β βββ ApiHandler.cs # Top-level dispatcher (service/bucket/object)
β βββ ServiceHandler.cs # ListBuckets
β βββ BucketHandler.cs # Bucket operations (incl. versioning, website, ACL, tags)
β βββ ObjectHandler.cs # Object operations + multipart + conditional PUT
β βββ ApiHelper.cs # Shared XML response helpers
βββ Storage/
β βββ StorageDriverBase.cs
β βββ DiskStorageDriver.cs # Atomic temp-then-move writes; ArrayPool + SubStream I/O
βββ Schema/ # Per-backend DDL: mssql Β· postgres Β· mysql Β· sqlite Β· cosmokv
βββ S3Objects/ # XML DTOs (request/response bodies)
βββ Logging/
βββ S3Logger.cs # Console/callback-based logger
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.3.5 | 84 | 6/16/2026 |
| 2.3.4 | 84 | 6/16/2026 |
| 2.3.3 | 79 | 6/16/2026 |
| 2.3.2 | 305 | 6/16/2026 |
| 2.3.1 | 95 | 6/14/2026 |
| 2.3.0 | 102 | 6/14/2026 |
| 2.2.0 | 96 | 6/14/2026 |
| 2.1.0 | 91 | 6/14/2026 |
| 2.0.1 | 91 | 6/14/2026 |
| 2.0.0 | 88 | 6/13/2026 |
| 1.9.25 | 97 | 5/24/2026 |
| 1.9.24 | 96 | 5/24/2026 |
| 1.9.23 | 103 | 5/23/2026 |
| 1.9.22 | 102 | 5/18/2026 |
| 1.9.21 | 124 | 5/18/2026 |
| 1.9.20 | 96 | 5/10/2026 |
| 1.9.19 | 103 | 5/10/2026 |
| 1.9.18 | 106 | 5/10/2026 |
| 1.9.17 | 93 | 5/3/2026 |
| 1.9.16 | 106 | 4/26/2026 |