Hosting your data
Publish the file you already have, unconverted, on a host that serves byte ranges to other websites: readers’ browsers read it from there directly. To open files already published, see Opening files.
What a host must do
Section titled “What a host must do”- Answer a
Rangerequest with206 Partial ContentandAccept-Ranges: bytes. A contact map on a host that ignores ranges is refused. - Let other sites read the file (CORS): allow
GET,HEADand theRangeheader, and exposeContent-LengthandContent-Range, the part most often forgotten. - Send the file as it is, with no
Content-Encoding: a.gzas the.gzit is. - Send
Content-Type: application/octet-stream, so that a CDN leaves the body alone. - Give a long cache lifetime to files whose name changes when their contents do.
A ranged read on such a host answers:
HTTP/1.1 206 Partial ContentAccept-Ranges: bytesContent-Range: bytes 0-1023/839405381Content-Length: 1024Content-Type: application/octet-streamAccess-Control-Allow-Origin: *Access-Control-Expose-Headers: Content-Length, Content-Range, Accept-Ranges, ETagand the OPTIONS preflight the browser sends first:
Access-Control-Allow-Origin: *Access-Control-Allow-Methods: GET, HEADAccess-Control-Allow-Headers: RangeAccess-Control-Max-Age: 86400Access-Control-Allow-Origin: * admits every copy of the viewer. A list admits only the origins it names: https://chromosomix.app (the site, its releases and its embeds), app://viewer (the desktop app), your own copy.
Indexes go beside their files
Section titled “Indexes go beside their files”Publish each index in its file’s folder, under the file’s name plus its suffix:
- a BAM’s
.bai(or.csi): without one, each window costs more requests; - a bgzipped text file’s tabix
.tbi(BED, bedGraph, GTF, VCF): without one, the file is read whole; - a FASTA’s
.fai, used over 64 MB. Without one, a FASTA is read whole up to 300 MB and refused past it; a bgzipped FASTA is always read whole.
samtools faidx genome.fa # writes genome.fa.faisamtools index reads.bam # writes reads.bam.baiAn index elsewhere is named after the file’s address: https://…/reads.bam#index=https://…/elsewhere/reads.bam.bai.
Amazon S3
Section titled “Amazon S3”-
Upload the files as they are, with a long cache lifetime for the data and a short one for files you will edit in place. Publish a new version under a new name.
Terminal window # The data: unchanged under its name, so cached for a yearaws s3 sync ./hic/ s3://my-lab/hic/ --exclude '*.json' \--content-type application/octet-stream \--cache-control 'public, max-age=31536000, immutable'# Stores and workspaces, which you will edit: cached for five minutesaws s3 sync ./hic/ s3://my-lab/hic/ --exclude '*' --include '*.json' \--content-type application/json \--cache-control 'public, max-age=300' -
Make the objects public with a bucket policy, which the account’s Block Public Access setting must not override: an anonymous read answering
403means one or the other.{"Version": "2012-10-17","Statement": [{"Sid": "PublicRead","Effect": "Allow","Principal": "*","Action": "s3:GetObject","Resource": "arn:aws:s3:::my-lab/hic/*"}]} -
Set the bucket’s CORS configuration, in the S3 console:
[{"AllowedOrigins": ["*"],"AllowedMethods": ["GET", "HEAD"],"AllowedHeaders": ["Range", "If-Match", "If-None-Match"],"ExposeHeaders": ["Content-Length", "Content-Range", "ETag", "Accept-Ranges"],"MaxAgeSeconds": 86400}]or from the command line:
Terminal window aws s3api put-bucket-cors --bucket my-lab --cors-configuration '{"CORSRules": [{"AllowedOrigins": ["*"],"AllowedMethods": ["GET", "HEAD"],"AllowedHeaders": ["Range", "If-Match", "If-None-Match"],"ExposeHeaders": ["Content-Length", "Content-Range", "ETag", "Accept-Ranges"],"MaxAgeSeconds": 86400}]}'Keep
MaxAgeSeconds, or every range request costs a preflight too. -
Check it with curl, then open
https://my-lab.s3.eu-west-3.amazonaws.com/hic/sample.mcool, ors3://my-lab/hic/sample.mcool.
put-bucket-cors replaces the bucket’s whole configuration: a bucket with a private prefix too needs both rules in one (A private bucket).
Cloudflare R2
Section titled “Cloudflare R2”R2, the host this project recommends for public data, works like S3 and does not charge for the bytes it sends.
- Allow public access in the bucket’s settings.
- Add the CORS rule above.
- Upload the files as they are, as
application/octet-streamwith noContent-Encoding. - Check the public address your readers will use.
MinIO, Ceph, Wasabi and Backblaze B2 take the same rule, in their own settings.
Google Cloud Storage
Section titled “Google Cloud Storage”Make the objects public, upload them as they are, and share gs:// addresses. Like https://storage.cloud.google.com/… addresses, they are read through Google’s JSON API and need no CORS. A https://storage.googleapis.com/… address, and connecting the bucket as a folder in the Files panel, need a CORS policy on the bucket: S3’s rule, in Google’s format. A private object opens once the reader signs in with a Google account that can read it.
A web server of your own
Section titled “A web server of your own”- Serve the files over HTTPS; plain
http://is read only fromlocalhostor127.0.0.1. - Answer
Rangewith206, as most static servers do for plain files. - Add the CORS headers above, and answer the
OPTIONSpreflight. - Turn off on-the-fly compression for the data files.
- Check it with curl.
Data beside your own copy of the viewer needs no CORS, but still needs byte ranges and no compression.
Hosts that work, and hosts that cannot
Section titled “Hosts that work, and hosts that cannot”| Host | What it needs |
|---|---|
| Amazon S3, by its virtual-hosted or path-style address | the CORS rule above |
| Azure Blob Storage, a public container or a SAS address | CORS on the storage account |
| A Dropbox share link | nothing: it is read from Dropbox’s own content host |
| A Google Drive share link | nothing for a public file on chromosomix.app; a private one needs the reader to sign in |
| GitHub Pages | cannot: it compresses these files and cannot be told not to |
| GEO and SRA | cannot: no ranges and no CORS. Deposit there for the record, and serve a copy from a bucket for viewing |
Drive and Dropbox are for reading, not publishing: each Drive request is slow and counts against a Google quota, and a connected Dropbox’s file links last four hours.
Check a host with curl
Section titled “Check a host with curl”Run these against the address you will publish, not a redirect:
URL=https://my-lab.s3.eu-west-3.amazonaws.com/hic/sample.mcool
# 1. Range support, the file's size and its type.curl -sSI "$URL" | grep -iE 'accept-ranges|content-length|content-type'
# 2. A real range read, and the CORS headers a browser would get.curl -sS -o /dev/null -D - -H 'Range: bytes=0-1023' -H 'Origin: https://chromosomix.app' "$URL" \ | grep -iE '^http/|content-range|access-control-'
# 3. What a browser gets, which always offers compression.curl -sSI -H 'Accept-Encoding: gzip, deflate, br' "$URL" | grep -iE 'content-encoding|content-length'- The first must show
accept-ranges: bytes, and the file’s size ascontent-length. - The second must show a
206, acontent-range, and anaccess-control-expose-headersnamingContent-Range. A200means the host ignores ranges. - The third must show no
content-encoding, and the file’s size ascontent-length. Without itsAccept-Encodingheader it would miss a host that fails a browser.
A host exposing neither Content-Length nor Content-Range still opens if it allows HEAD, at one more request per file.
What it costs
Section titled “What it costs”A reader costs a few requests, not a download: a map’s header and indexes, then one bounded read per new place. The whole-genome view reads the whole file, so it waits for a button. read on the status bar shows a file’s requests and bytes, in total and for the last view.
Object stores bill requests and the bytes sent out (egress), which grow with a popular dataset; R2 does not charge for egress. Keep MaxAgeSeconds and long cache lifetimes.
Public or private
Section titled “Public or private”Everything above is for public files, readable by anyone with the address. Readers can also sign in to Google or Dropbox to read their own private files (Privacy).
A public bucket that can be browsed
Section titled “A public bucket that can be browsed”Opening a file needs s3:GetObject. Browsing its folder with connect a bucket… also needs s3:ListBucket, which most published buckets do not grant. Add it, limited to the prefix so the rest of the bucket stays unlisted:
{ "Sid": "PublicBrowse", "Effect": "Allow", "Principal": "*", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::my-lab", "Condition": { "StringLike": { "s3:prefix": ["hic/*"] } }}The CORS rule stays as it is. Without this grant every file still opens by URL, and the viewer says which grant is missing.
A private bucket
Section titled “A private bucket”Each reader gives the viewer a key. On the landing page, connect a bucket… takes the prefix (https:// or s3://bucket/prefix/), and additional settings under it the region, the access key ID, the secret key and, for temporary credentials, a session token. Requests under that prefix are then signed; the rest are not.
Give readers a key that can only read and list that prefix:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::my-lab/collections/*" }, { "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::my-lab", "Condition": { "StringLike": { "s3:prefix": ["collections/*"] } } } ]}Signed requests need a second CORS rule, after the public one and in the same configuration, since S3 uses the first rule that matches:
[ { "AllowedOrigins": ["*"], "AllowedMethods": ["GET", "HEAD"], "AllowedHeaders": ["Range", "If-Match", "If-None-Match"], "ExposeHeaders": ["Content-Length", "Content-Range", "ETag", "Accept-Ranges"], "MaxAgeSeconds": 86400 }, { "AllowedOrigins": ["https://chromosomix.app"], "AllowedMethods": ["GET", "HEAD"], "AllowedHeaders": [ "Range", "If-Match", "If-None-Match", "Authorization", "x-amz-date", "x-amz-content-sha256", "x-amz-security-token" ], "ExposeHeaders": ["Content-Length", "Content-Range", "ETag", "Accept-Ranges"], "MaxAgeSeconds": 86400 }]Add your own copy’s address, or app://viewer for the desktop app, to its AllowedOrigins if readers use them. The preflight a browser sends before a signed read checks it with no key, and must print 200:
curl -sS -o /dev/null -w '%{http_code}\n' -X OPTIONS "$URL" \ -H 'Origin: https://chromosomix.app' \ -H 'Access-Control-Request-Method: GET' \ -H 'Access-Control-Request-Headers: authorization,x-amz-date,x-amz-content-sha256'Links that expire
Section titled “Links that expire”A presigned S3 address, a signed Google Cloud Storage address or an Azure SAS works until it expires; then the host answers 403. Until then anyone it reaches can read the file, through every link written from that view too: use one for a colleague, not a citation.
Links to share
Section titled “Links to share”A link carries addresses, never data: whoever opens it reads your host, with their own access.
- A place in a file. Go there and use Share this tab as a link, or write
https://chromosomix.app/#uri=<address>&view=region®ion=chr2:10000000-14000000with the keys in Link keys. - A composed figure. Put a workspace saved with Save every tab as a file beside the data, and link to
https://chromosomix.app/?config=<its address>(Sessions, workspaces and links). - The whole collection. List it in a data store and link to
https://chromosomix.app/?store=<its address>. - In a paper. Name a release, kept for good:
https://chromosomix.app/v/<tag>/, with the same query and fragment. - In a web page. Add
?embed=1and put the link in an<iframe>(Embedding in a web page).
Documentation of chromosomix v0.20.0 · built from 40ec1ec · source
