Repository: chrovis/cljam
Pull request #132 (2018-06-21 07:02:38)

Support more source types
Current `cljam.io` basically supports only a string file path as input/output.
This PR adds support for more source types such as `java.io.File` or `java.net.URL`.

```clojure
(require '[cljam.io.sam :as sam])

(with-open [r (sam/reader (java.io.File. "path/to/test.sam"))]
  (first (sam/read-alignments r)))

(with-open [r (sam/reader (java.net.URL. "file:/path/to/test.sam"))]
  (first (sam/read-alignments r)))
```

http source also can be used except fasta/2bit readers.

```clojure
(with-open [r (sam/reader (java.net.URL. "http://example.com/path/to/test.sam"))]
  (first (sam/read-alignments r)))
```

Reader/writer keep a file location as `java.net.URL`, and protocol methods
`{reader,writer}-path` are renamed to `{reader,writer}-url`. This may be
breaking changes.

I confirmed `test :all` passed.


Patch:
diff --git a/project.clj b/project.clj
index d861771..2295df3 100644
--- a/project.clj
+++ b/project.clj
@@ -16,7 +16,8 @@
                                   [cavia "0.5.0"]
                                   [criterium "0.4.4"]
                                   [net.totakke/libra "0.1.1"]
-                                  [org.tcrawley/dynapath "1.0.0"]]
+                                  [org.tcrawley/dynapath "1.0.0"]
+                                  [se.haleby/stub-http "0.2.5"]]
                    :plugins [[lein-binplus "0.6.2" :exclusions [org.clojure/clojure]]
                              [lein-codox "0.10.3"]
                              [lein-marginalia "0.9.1" :exclusions [org.clojure/clojure]]
diff --git a/src/cljam/algo/level.clj b/src/cljam/algo/level.clj
index 62c4a1c..d986aa0 100644
--- a/src/cljam/algo/level.clj
+++ b/src/cljam/algo/level.clj
@@ -42,7 +42,7 @@
   "Create a path to the cache file."
   [rdr i]
   (->> i
-       (format "%s_%06d.bam" (util/basename (protocols/reader-path rdr)))
+       (format "%s_%06d.bam" (util/basename (protocols/reader-url rdr)))
        (cio/file util/temp-dir)
        (.getCanonicalPath)))
 
diff --git a/src/cljam/algo/sorter.clj b/src/cljam/algo/sorter.clj
index 2a90801..e6d799e 100644
--- a/src/cljam/algo/sorter.clj
+++ b/src/cljam/algo/sorter.clj
@@ -76,7 +76,7 @@
 (defn- split*
   "Splits SAM/BAM files with appropriate reader/writer functions."
   [rdr chunk-size name-fn mode sort-fn]
-  (let [block? (same-type? (protocols/reader-path rdr) (name-fn 0))
+  (let [block? (same-type? (protocols/reader-url rdr) (name-fn 0))
         read-fn (if block?
                   (fn [r] (sam/read-blocks r {} {:mode mode}))
                   sam/read-alignments)
@@ -122,7 +122,7 @@
 (defn- merge*
   "Merges multiple SAM/BAM files with appropriate reader/writer functions."
   [wtr hdr files mode key-fn]
-  (let [block? (apply same-type? (protocols/writer-path wtr) files)
+  (let [block? (apply same-type? (protocols/writer-url wtr) files)
         read-fn (if block?
                   (fn [r] (sam/read-blocks r {} {:mode mode}))
                   sam/read-alignments)
@@ -151,7 +151,7 @@
   :coordinate and :queryname are available for mode."
   [rdr wtr {:keys [mode chunk-size cache-fmt] :or {cache-fmt :bam}}]
   (let [name-fn (->> rdr
-                     protocols/reader-path
+                     protocols/reader-url
                      util/basename
                      (partial gen-cache-filename (name cache-fmt)))
         key-fn ({header/order-coordinate (partial coordinate-key (refmap (sam/read-refs rdr)))
diff --git a/src/cljam/io/bam/core.clj b/src/cljam/io/bam/core.clj
index 39f0515..99ef629 100644
--- a/src/cljam/io/bam/core.clj
+++ b/src/cljam/io/bam/core.clj
@@ -1,15 +1,15 @@
 (ns cljam.io.bam.core
   "The core of BAM features."
-  (:require [clojure.java.io :as cio]
-            [clojure.string :as cstr]
+  (:require [clojure.string :as cstr]
             [cljam.io.bam [common :as common]
                           [reader :as reader]
                           [writer :as writer]]
             [cljam.io.bam-index :as bai]
-            [cljam.io.util.lsb :as lsb])
+            [cljam.io.util.bgzf :as bgzf]
+            [cljam.io.util.lsb :as lsb]
+            [cljam.util :as util])
   (:import java.util.Arrays
            [java.io DataInputStream DataOutputStream IOException FileNotFoundException]
-           [bgzf4j BGZFInputStream BGZFOutputStream]
            cljam.io.bam.reader.BAMReader
            cljam.io.bam.writer.BAMWriter))
 
@@ -17,40 +17,41 @@
 ;; -------
 
 (defn- bam-index
-  "Load an index file (BAI) for the given BAM file."
-  [bam-path]
-  (if-let [bai-path (->> ["$1.bai" ".bai" "$1.BAI" ".BAI"]
-                         (eduction
-                          (comp
-                           (map #(cstr/replace bam-path #"(?i)(\.bam)$" %))
-                           (filter #(.isFile (cio/file %)))))
-                         first)]
-    (bai/bam-index bai-path)
-    (throw (FileNotFoundException. (str "Could not find BAM Index file for " bam-path)))))
+  "Loads an index file (BAI) for the given BAM URL."
+  [bam-url]
+  (or (->> ["$1.bai" ".bai" "$1.BAI" ".BAI"]
+           (eduction
+            (comp
+             (map #(cstr/replace (str bam-url) #"(?i)(\.bam)$" %))
+             (map util/as-url)
+             (keep #(try (bai/bam-index %) (catch FileNotFoundException _)))))
+           first)
+      (throw (FileNotFoundException.
+              (str "Could not find BAM Index file for " bam-url)))))
 
 (defn ^BAMReader reader
-  "Creates a `cljam.io.bam.BAMReader` instance for the given path."
+  "Creates a `cljam.io.bam.BAMReader` instance for the given file."
   [f]
-  (let [rdr (BGZFInputStream. (cio/file f))
+  (let [rdr (bgzf/bgzf-input-stream f)
         data-rdr (DataInputStream. rdr)]
     (when-not (Arrays/equals ^bytes (lsb/read-bytes data-rdr 4) (.getBytes ^String common/bam-magic))
       (throw (IOException. "Invalid BAM file")))
     (let [{:keys [header refs]} (reader/load-headers data-rdr)
-          index-delay (delay (bam-index f))]
-      (BAMReader. (.getAbsolutePath (cio/file f))
+          index-delay (delay (bam-index (util/as-url f)))]
+      (BAMReader. (util/as-url f)
                   header refs rdr data-rdr index-delay (.getFilePointer rdr)))))
 
 (defn ^BAMReader clone-reader
   "Clones bam reader sharing persistent objects."
   [^BAMReader rdr]
-  (let [bgzf-rdr (BGZFInputStream. (cio/file (.f rdr)))
+  (let [bgzf-rdr (bgzf/bgzf-input-stream (.url rdr))
         data-rdr (DataInputStream. bgzf-rdr)]
     (.seek bgzf-rdr (.start-pos rdr))
-    (BAMReader. (.f rdr) (.header rdr) (.refs rdr) bgzf-rdr data-rdr (.index-delay rdr) (.start-pos rdr))))
+    (BAMReader. (.url rdr) (.header rdr) (.refs rdr) bgzf-rdr data-rdr (.index-delay rdr) (.start-pos rdr))))
 
 ;; Writing
 ;; -------
 
 (defn ^BAMWriter writer [f]
-  (BAMWriter. (.getAbsolutePath (cio/file f))
-              (DataOutputStream. (BGZFOutputStream. (cio/file f)))))
+  (BAMWriter. (util/as-url f)
+              (DataOutputStream. (bgzf/bgzf-output-stream f))))
diff --git a/src/cljam/io/bam/reader.clj b/src/cljam/io/bam/reader.clj
index 7cf19ec..cc622a9 100644
--- a/src/cljam/io/bam/reader.clj
+++ b/src/cljam/io/bam/reader.clj
@@ -17,13 +17,13 @@
 ;; BAMReader
 ;; ---------
 
-(deftype BAMReader [f header refs reader data-reader index-delay start-pos]
+(deftype BAMReader [url header refs reader data-reader index-delay start-pos]
   Closeable
   (close [this]
     (.close ^Closeable (.reader this)))
   protocols/IReader
-  (reader-path [this]
-    (.f this))
+  (reader-url [this]
+    (.url this))
   (read [this]
     (protocols/read this {}))
   (read [this region]
diff --git a/src/cljam/io/bam/writer.clj b/src/cljam/io/bam/writer.clj
index ec85c26..b97e2b2 100644
--- a/src/cljam/io/bam/writer.clj
+++ b/src/cljam/io/bam/writer.clj
@@ -14,13 +14,13 @@
 ;; BAMWriter
 ;;
 
-(deftype BAMWriter [f writer]
+(deftype BAMWriter [url writer]
   Closeable
   (close [this]
     (.close ^Closeable (.writer this)))
   protocols/IWriter
-  (writer-path [this]
-    (.f this))
+  (writer-url [this]
+    (.url this))
   protocols/IAlignmentWriter
   (write-header [this header]
     (write-header* this header))
diff --git a/src/cljam/io/bam_index/core.clj b/src/cljam/io/bam_index/core.clj
index e9baa27..1b983ca 100644
--- a/src/cljam/io/bam_index/core.clj
+++ b/src/cljam/io/bam_index/core.clj
@@ -5,16 +5,17 @@
             [cljam.io.bam-index [common :refer :all]
                                 [chunk :as chunk]
                                 [reader :as reader]
-                                [writer :as writer]])
+                                [writer :as writer]]
+            [cljam.util :as util])
   (:import [java.io DataOutputStream FileOutputStream]
            cljam.io.bam_index.reader.BAIReader
            cljam.io.bam_index.writer.BAIWriter))
 
-(deftype BAMIndex [f bidx lidx])
+(deftype BAMIndex [url bidx lidx])
 
 (defn bam-index [f]
   (let [{:keys [bidx lidx]} (with-open [r ^BAIReader (reader/reader f)] (reader/read-all-index! r))]
-    (->BAMIndex f bidx lidx)))
+    (->BAMIndex (util/as-url f) bidx lidx)))
 
 ;; ## Reading
 
@@ -79,7 +80,7 @@
   [f refs]
   (BAIWriter. (DataOutputStream. (FileOutputStream. (cio/file f)))
               refs
-              (.getAbsolutePath (cio/file f))))
+              (util/as-url f)))
 
 (defn create-index
   "Creates a BAM index file from the alignments and references data."
@@ -88,6 +89,6 @@
     (try
       (writer/write-index! w alns)
       (catch Exception e (do
-                           (cio/delete-file (.f w))
+                           (cio/delete-file (.url w))
                            (logging/error "Failed to create BAM index")
                            (throw e))))))
diff --git a/src/cljam/io/bam_index/reader.clj b/src/cljam/io/bam_index/reader.clj
index 6761551..a13046c 100644
--- a/src/cljam/io/bam_index/reader.clj
+++ b/src/cljam/io/bam_index/reader.clj
@@ -2,18 +2,19 @@
   (:require [clojure.java.io :as cio]
             [cljam.io.util.lsb :as lsb]
             [cljam.io.bam-index.common :refer [bai-magic]]
-            [cljam.io.bam-index.chunk :as chunk])
+            [cljam.io.bam-index.chunk :as chunk]
+            [cljam.util :as util])
   (:import java.util.Arrays
-           [java.io DataInputStream FileInputStream Closeable IOException]
+           [java.io FileInputStream Closeable IOException]
            [cljam.io.bam_index.chunk Chunk]))
 
-(deftype BAIReader [f reader]
+(deftype BAIReader [url reader]
   Closeable
   (close [this]
     (.close ^Closeable (.reader this))))
 
 (defn- skip-chunks!
-  [^DataInputStream rdr]
+  [rdr]
   (let [n-chunks (lsb/read-int rdr)]
     (loop [i 0]
       (when (< i n-chunks)
@@ -21,7 +22,7 @@
         (recur (inc i))))))
 
 (defn- skip-bin-index!
-  [^DataInputStream rdr]
+  [rdr]
   (let [n-bidx (lsb/read-int rdr)]
     (loop [i 0]
       (when (< i n-bidx)
@@ -30,7 +31,7 @@
         (recur (inc i))))))
 
 (defn- skip-linear-index!
-  [^DataInputStream rdr]
+  [rdr]
   (let [n-lidx (lsb/read-int rdr)]
     (loop [i 0]
       (when (< i n-lidx)
@@ -38,7 +39,7 @@
         (recur (inc i))))))
 
 (defn- skip-index!
-  [^DataInputStream rdr n]
+  [rdr n]
   (loop [i 0]
     (when (< i n)
       (skip-bin-index! rdr)
@@ -46,7 +47,7 @@
       (recur (inc i)))))
 
 (defn- read-chunks!
-  [^DataInputStream rdr]
+  [rdr]
   (let [n (lsb/read-int rdr)]
    (loop [i 0, chunks []]
      (if (< i n)
@@ -54,7 +55,7 @@
        chunks))))
 
 (defn- read-bin-index**!
-  [^DataInputStream rdr]
+  [rdr]
   (let [n (lsb/read-int rdr)]
     (loop [i 0, bidx []]
       (if (< i n)
@@ -64,7 +65,7 @@
         bidx))))
 
 (defn- read-bin-index*!
-  [^DataInputStream rdr ref-idx]
+  [rdr ref-idx]
   (let [n-ref (lsb/read-int rdr)]
     (when (>= ref-idx n-ref)
       (throw (IndexOutOfBoundsException. "The reference index number is invalid")))
@@ -72,7 +73,7 @@
     (read-bin-index**! rdr)))
 
 (defn- read-linear-index**!
-  [^DataInputStream rdr]
+  [rdr]
   (let [n (lsb/read-int rdr)]
     (loop [i 0, lidx []]
       (if (< i n)
@@ -80,7 +81,7 @@
         lidx))))
 
 (defn- read-linear-index*!
-  [^DataInputStream rdr ref-idx]
+  [rdr ref-idx]
   (let [n-ref (lsb/read-int rdr)]
     (when (>= ref-idx n-ref)
       (throw (IndexOutOfBoundsException. "The reference index number is invalid")))
@@ -113,7 +114,10 @@
      :lidx lidx}))
 
 (defn reader [f]
-  (let [r (DataInputStream. (FileInputStream. (cio/file f)))]
+  (let [url (util/as-url f)
+        r (if (= (.getProtocol url) "file")
+            (FileInputStream. (cio/file url))
+            (.openStream url))]
     (when-not (Arrays/equals ^bytes (lsb/read-bytes r 4) (.getBytes ^String bai-magic))
       (throw (IOException. "Invalid BAI file")))
-    (->BAIReader f r)))
+    (->BAIReader url r)))
diff --git a/src/cljam/io/bam_index/writer.clj b/src/cljam/io/bam_index/writer.clj
index 4668977..621fc58 100644
--- a/src/cljam/io/bam_index/writer.clj
+++ b/src/cljam/io/bam_index/writer.clj
@@ -17,7 +17,7 @@
 ;; BAIWriter
 ;; ---------
 
-(deftype BAIWriter [^DataOutputStream writer refs f]
+(deftype BAIWriter [^DataOutputStream writer refs url]
   Closeable
   (close [this]
     (.close writer)))
diff --git a/src/cljam/io/bcf/reader.clj b/src/cljam/io/bcf/reader.clj
index 65a744b..3166631 100644
--- a/src/cljam/io/bcf/reader.clj
+++ b/src/cljam/io/bcf/reader.clj
@@ -1,24 +1,26 @@
 (ns cljam.io.bcf.reader
   (:refer-clojure :exclude [read])
-  (:require [clojure.java.io :as cio]
-            [clojure.string :as cstr]
+  (:require [clojure.string :as cstr]
             [clojure.tools.logging :as logging]
             [cljam.io.protocols :as protocols]
+            [cljam.io.util.bgzf :as bgzf]
             [cljam.io.util.lsb :as lsb]
             [cljam.io.vcf.reader :as vcf-reader]
-            [cljam.io.vcf.util :as vcf-util])
+            [cljam.io.vcf.util :as vcf-util]
+            [cljam.util :as util])
   (:import [java.io Closeable IOException]
+           [java.net URL]
            [java.nio ByteBuffer]
            [bgzf4j BGZFInputStream]))
 
 (declare read-variants meta-info)
 
-(deftype BCFReader [^String f meta-info header ^BGZFInputStream reader ^long start-pos]
+(deftype BCFReader [^URL url meta-info header ^BGZFInputStream reader ^long start-pos]
   Closeable
   (close [this]
     (.close ^Closeable (.reader this)))
   protocols/IReader
-  (reader-path [this] (.f this))
+  (reader-url [this] (.url this))
   (read [this] (protocols/read this {}))
   (read [this option] (read-variants this option))
   (indexed? [_] false)
@@ -64,7 +66,7 @@
   ensure the Reader is properly closed.
    Throws IOException if failed to parse BCF file format."
   [f]
-  (let [rdr (BGZFInputStream. (cio/file f))
+  (let [rdr (bgzf/bgzf-input-stream f)
         magic (lsb/read-bytes rdr 5)]
     (if (= (seq magic) (map byte "BCF\2\2"))
       (let [hlen (lsb/read-int rdr)
@@ -73,7 +75,7 @@
           (let [{:keys [header meta]} (->> (String. ^bytes header-buf 0 (int (dec hlen)))
                                            cstr/split-lines
                                            parse-meta-and-header)]
-            (BCFReader. (.getAbsolutePath (cio/file f)) meta header rdr (.getFilePointer rdr)))
+            (BCFReader. (util/as-url f) meta header rdr (.getFilePointer rdr)))
           (do
             (.close rdr)
             (throw (IOException. (str "Invalid file format. BCF header must be NULL-terminated."))))))
@@ -210,13 +212,11 @@
 
 (defn- read-data-lines
   "Reads data from BCF file and returns them as a lazy-sequence of maps."
-  [^BGZFInputStream rdr read-fn pos]
-  (.seek rdr pos)
+  [^BGZFInputStream rdr read-fn]
   (when (pos? (.available rdr))
-    (let [data (read-fn rdr)
-          next-pos (.getFilePointer rdr)]
+    (let [data (read-fn rdr)]
       (cons data
-            (lazy-seq (read-data-lines rdr read-fn next-pos))))))
+            (lazy-seq (read-data-lines rdr read-fn))))))
 
 (defn- meta->map
   "Creates a map for searching meta-info with indices."
@@ -261,5 +261,4 @@
                     :shallow (partial parse-data-line-shallow contigs)
                     :raw identity)]
      (read-data-lines (.reader rdr)
-                      (fn [rdr] (parse-fn (read-data-line-buffer rdr)))
-                      (.start-pos rdr)))))
+                      (fn [rdr] (parse-fn (read-data-line-buffer rdr)))))))
diff --git a/src/cljam/io/bcf/writer.clj b/src/cljam/io/bcf/writer.clj
index 4e36752..601104e 100644
--- a/src/cljam/io/bcf/writer.clj
+++ b/src/cljam/io/bcf/writer.clj
@@ -1,22 +1,23 @@
 (ns cljam.io.bcf.writer
-  (:require [clojure.java.io :as cio]
-            [clojure.string :as cstr]
+  (:require [clojure.string :as cstr]
             [cljam.io.protocols :as protocols]
+            [cljam.io.util.bgzf :as bgzf]
             [cljam.io.util.lsb :as lsb]
             [cljam.io.vcf.writer :as vw]
-            [cljam.io.vcf.util :as vcf-util])
+            [cljam.io.vcf.util :as vcf-util]
+            [cljam.util :as util])
   (:import [java.io Closeable IOException DataOutputStream]
-           [java.nio ByteBuffer ByteOrder]
-           [bgzf4j BGZFOutputStream]))
+           [java.net URL]
+           [java.nio ByteBuffer ByteOrder]))
 
 (declare write-variants)
 
-(deftype BCFWriter [^String f meta-info header ^DataOutputStream writer]
+(deftype BCFWriter [^URL url meta-info header ^DataOutputStream writer]
   Closeable
   (close [this]
     (.close ^Closeable (.writer this)))
   protocols/IWriter
-  (writer-path [this] (.f this))
+  (writer-url [this] (.url this))
   protocols/IVariantWriter
   (write-variants [this variants]
     (write-variants this variants)))
@@ -62,8 +63,8 @@
                              {:file-date \"20090805\", :source \"myImpu...\" ...}
                              [\"CHROM\" \"POS\" \"ID\" \"REF\" \"ALT\" ...])]
        (WRITING-BCF))"
-  [^String f meta-info header]
-  (let [bos (BGZFOutputStream. f)
+  [f meta-info header]
+  (let [bos (bgzf/bgzf-output-stream f)
         dos (DataOutputStream. bos)
         indexed-meta (-> meta-info
                          (update :contig (fn [xs] (map-indexed (fn [i m] (assoc m :idx (str i))) xs)))
@@ -74,7 +75,7 @@
                                                      (map-indexed (fn [i m] (assoc m :idx (str (inc i)))) others)))))
                          (update :info (fn [xs] (map-indexed (fn [i m] (assoc m :idx (str i))) xs)))
                          (update :format (fn [xs] (map-indexed (fn [i m] (assoc m :idx (str i))) xs))))
-        w (BCFWriter. (.getAbsolutePath (cio/file f)) indexed-meta header dos)]
+        w (BCFWriter. (util/as-url f) indexed-meta header dos)]
     (write-file-header w)
     w))
 
diff --git a/src/cljam/io/bed.clj b/src/cljam/io/bed.clj
index c3efad3..ed46e78 100644
--- a/src/cljam/io/bed.clj
+++ b/src/cljam/io/bed.clj
@@ -9,16 +9,17 @@
             [cljam.util.chromosome :as chr]
             [cljam.util.region :as region]
             [clojure.tools.logging :as logging])
-  (:import [java.io BufferedReader BufferedWriter Closeable]))
+  (:import [java.net URL]
+           [java.io BufferedReader BufferedWriter Closeable]))
 
 (declare read-fields write-fields)
 
-(defrecord BEDReader [^BufferedReader reader ^String f]
+(defrecord BEDReader [^BufferedReader reader ^URL url]
   Closeable
   (close [this]
     (.close ^Closeable (.reader this)))
   protocols/IReader
-  (reader-path [this] (.f this))
+  (reader-url [this] (.url this))
   (read [this] (protocols/read this {}))
   (read [this option] (read-fields this))
   (indexed? [_] false)
@@ -32,26 +33,24 @@
                          (or (not end) (<= (:end m) end))))
             (read-fields this))))
 
-(defrecord BEDWriter [^BufferedWriter writer ^String f]
+(defrecord BEDWriter [^BufferedWriter writer ^URL url]
   Closeable
   (close [this]
     (.close ^Closeable (.writer this)))
   protocols/IWriter
-  (writer-path [this] (.getAbsolutePath (cio/file (.f this)))))
+  (writer-url [this] (.url this)))
 
 (defn ^BEDReader reader
   "Returns an open cljam.io.bed.BEDReader of f. Should be used inside with-open
   to ensure the reader is properly closed."
   [f]
-  (let [abs (.getAbsolutePath (cio/file f))]
-    (BEDReader. (cio/reader (util/compressor-input-stream abs)) abs)))
+  (BEDReader. (cio/reader (util/compressor-input-stream f)) (util/as-url f)))
 
 (defn ^BEDWriter writer
   "Returns an open cljam.io.bed.BEDWriter of f. Should be used inside with-open
   to ensure the writer is properly closed."
   [f]
-  (let [abs (.getAbsolutePath (cio/file f))]
-  (BEDWriter. (cio/writer (util/compressor-output-stream abs)) abs)))
+  (BEDWriter. (cio/writer (util/compressor-output-stream f)) (util/as-url f)))
 
 (def ^:const bed-columns
   [:chr :start :end :name :score :strand :thick-start :thick-end :item-rgb :block-count :block-sizes :block-starts])
diff --git a/src/cljam/io/dict/core.clj b/src/cljam/io/dict/core.clj
index 5c340e9..f1c715c 100644
--- a/src/cljam/io/dict/core.clj
+++ b/src/cljam/io/dict/core.clj
@@ -2,7 +2,8 @@
   "The core of dictionary features."
   (:require [clojure.java.io :as cio]
             [clojure.tools.logging :as logging]
-            [cljam.io.dict.writer :as writer])
+            [cljam.io.dict.writer :as writer]
+            [cljam.util :as util])
   (:import cljam.io.dict.writer.DICTWriter))
 
 ;; Reading
@@ -18,7 +19,7 @@
   `with-open` to ensure the writer is properly closed."
   [f]
   (DICTWriter. (cio/writer f)
-               (.getAbsolutePath (cio/file f))))
+               (util/as-url f)))
 
 (defn create-dict
   "Creates a FASTA sequence dictionary file (.dict) from the specified FASTA
@@ -28,6 +29,6 @@
     (try
       (writer/write-dict! w headers sequences ur)
       (catch Exception e (do
-                           (cio/delete-file (.f w))
+                           (cio/delete-file (.url w))
                            (logging/error "Failed to create dictionary")
                            (throw e))))))
diff --git a/src/cljam/io/dict/writer.clj b/src/cljam/io/dict/writer.clj
index fd3db9b..abcd0cc 100644
--- a/src/cljam/io/dict/writer.clj
+++ b/src/cljam/io/dict/writer.clj
@@ -8,7 +8,7 @@
 ;; DICTWriter
 ;; ----------
 
-(deftype DICTWriter [^java.io.BufferedWriter writer f]
+(deftype DICTWriter [^java.io.BufferedWriter writer url]
   java.io.Closeable
   (close [this]
     (.close writer)))
diff --git a/src/cljam/io/fasta/core.clj b/src/cljam/io/fasta/core.clj
index 6c06e99..f61adec 100644
--- a/src/cljam/io/fasta/core.clj
+++ b/src/cljam/io/fasta/core.clj
@@ -13,33 +13,33 @@
 ;; -------
 
 (defn- fasta-index
-  [fasta-path]
+  [fasta-url]
   (let [fasta-exts #"(?i)(\.(fa|fasta|fas|fsa|seq|fna|faa|ffn|frn|mpfa)?)$"]
-    (if-let [fai-path (->> ["$1.fai" ".fai" "$1.FAI" ".FAI"]
-                           (eduction
-                            (comp
-                             (map #(cstr/replace fasta-path fasta-exts %))
-                             (filter #(.isFile (cio/file %)))))
-                           first)]
-      (fai/reader fai-path)
-      (throw (FileNotFoundException.
-              (str "Could not find FASTA Index file for " fasta-path))))))
+    (or (->> ["$1.fai" ".fai" "$1.FAI" ".FAI"]
+             (eduction
+              (comp
+               (map #(cstr/replace (str fasta-url) fasta-exts %))
+               (map util/as-url)
+               (keep #(try (fai/reader %) (catch FileNotFoundException _)))))
+             first)
+        (throw (FileNotFoundException.
+                (str "Could not find FASTA Index file for " fasta-url))))))
 
 (defn ^FASTAReader reader
   [f]
   (let [f (.getAbsolutePath (cio/file f))]
     (FASTAReader. (RandomAccessFile. f "r")
                   (util/compressor-input-stream f)
-                  f
-                  (delay (fasta-index f)))))
+                  (util/as-url f)
+                  (delay (fasta-index (util/as-url f))))))
 
 (defn ^FASTAReader clone-reader
   "Clones fasta reader sharing persistent objects."
   [^FASTAReader rdr]
-  (let [f (.f rdr)
-        raf (RandomAccessFile. ^String f "r")
-        stream (util/compressor-input-stream f)]
-    (FASTAReader. raf stream f (.index-delay rdr))))
+  (let [url (.url rdr)
+        raf (RandomAccessFile. (cio/as-file url) "r")
+        stream (util/compressor-input-stream url)]
+    (FASTAReader. raf stream url (.index-delay rdr))))
 
 (defn read-headers
   [^FASTAReader rdr]
@@ -78,7 +78,7 @@
 
 (extend-type FASTAReader
   protocols/IReader
-  (reader-path [this] (.f this))
+  (reader-url [this] (.url this))
   (read
     ([this] (protocols/read this {}))
     ([this option] (protocols/read-all-sequences this option)))
diff --git a/src/cljam/io/fasta/reader.clj b/src/cljam/io/fasta/reader.clj
index f11794d..0947984 100644
--- a/src/cljam/io/fasta/reader.clj
+++ b/src/cljam/io/fasta/reader.clj
@@ -10,7 +10,7 @@
 ;; FASTAReader
 ;; -----------
 
-(deftype FASTAReader [reader stream f index-delay]
+(deftype FASTAReader [reader stream url index-delay]
   java.io.Closeable
   (close [this]
     (.close ^java.io.Closeable (.reader this))
diff --git a/src/cljam/io/fasta/writer.clj b/src/cljam/io/fasta/writer.clj
index a31d150..a4dfe96 100644
--- a/src/cljam/io/fasta/writer.clj
+++ b/src/cljam/io/fasta/writer.clj
@@ -7,15 +7,15 @@
 
 (declare write-sequences)
 
-(deftype FASTAWriter [f ^int cols writer index-writer curr-offset]
+(deftype FASTAWriter [url ^int cols writer index-writer curr-offset]
   Closeable
   (close [this]
     (.close ^Closeable (.writer this))
     (when-let [iw (.index-writer this)]
       (.close ^Closeable iw)))
   protocols/IWriter
-  (writer-path [this]
-    (.f this))
+  (writer-url [this]
+    (.url this))
   protocols/ISequenceWriter
   (write-sequences [this seqs]
     (write-sequences this seqs)))
@@ -25,7 +25,7 @@
   (let [abs-f (.getAbsolutePath (cio/file f))
         writer (cio/writer (util/compressor-output-stream abs-f))
         index-writer (when create-index? (cio/writer (str abs-f ".fai")))]
-    (FASTAWriter. abs-f cols writer index-writer (volatile! 0))))
+    (FASTAWriter. (util/as-url abs-f) cols writer index-writer (volatile! 0))))
 
 (defn- write-name
   [^FASTAWriter w ^String n]
diff --git a/src/cljam/io/fasta_index/core.clj b/src/cljam/io/fasta_index/core.clj
index 9b5206b..d3dfa50 100644
--- a/src/cljam/io/fasta_index/core.clj
+++ b/src/cljam/io/fasta_index/core.clj
@@ -14,7 +14,7 @@
   [f]
   (FAIWriter.
    (cio/writer f)
-   (.getAbsolutePath (cio/file f))))
+   (util/as-url f)))
 
 (defn create-index
   "Creates a FASTA index file from the sequences."
@@ -24,7 +24,7 @@
     (try
       (writer/write-index! r w)
       (catch Exception e (do
-                           (cio/delete-file (.f w))
+                           (cio/delete-file (.url w))
                            (logging/error "Failed to create FASTA index")
                            (throw e))))))
 
@@ -35,7 +35,7 @@
   (FAIReader.
    (with-open [rdr (cio/reader f)]
      (reader/parse-fai rdr))
-   (.getAbsolutePath (cio/file f))))
+   (util/as-url f)))
 
 (defn get-header
   [^FAIReader fai name]
diff --git a/src/cljam/io/fasta_index/reader.clj b/src/cljam/io/fasta_index/reader.clj
index a439da7..e150601 100644
--- a/src/cljam/io/fasta_index/reader.clj
+++ b/src/cljam/io/fasta_index/reader.clj
@@ -4,7 +4,7 @@
 
 ;;;; FAIReader
 
-(deftype FAIReader [indices f])
+(deftype FAIReader [indices url])
 
 ;;;; Reader
 
diff --git a/src/cljam/io/fasta_index/writer.clj b/src/cljam/io/fasta_index/writer.clj
index 8392408..d516bed 100644
--- a/src/cljam/io/fasta_index/writer.clj
+++ b/src/cljam/io/fasta_index/writer.clj
@@ -6,7 +6,7 @@
 
 ;;;; FAIWriter
 
-(deftype FAIWriter [writer f]
+(deftype FAIWriter [writer url]
   java.io.Closeable
   (close [this]
     (.close ^java.io.Closeable (.writer this))))
diff --git a/src/cljam/io/fastq.clj b/src/cljam/io/fastq.clj
index 1eab25f..c6c36aa 100644
--- a/src/cljam/io/fastq.clj
+++ b/src/cljam/io/fastq.clj
@@ -9,42 +9,38 @@
 
 (declare read-sequences write-sequences)
 
-(deftype FASTQReader [reader f]
+(deftype FASTQReader [reader url]
   Closeable
   (close [this]
     (.close ^Closeable (.reader this)))
   protocols/IReader
-  (reader-path [this] (.f this))
+  (reader-url [this] (.url this))
   (read [this] (read-sequences this))
   (read [this opts] (read-sequences this opts))
   (indexed? [_] false))
 
-(deftype FASTQWriter [writer f]
+(deftype FASTQWriter [writer url]
   Closeable
   (close [this]
     (.close ^Closeable (.writer this)))
   protocols/IWriter
-  (writer-path [this] (.f this)))
+  (writer-url [this] (.url this)))
 
 (defn ^FASTQReader reader
   "Returns an open cljam.io.fastq.FASTQReader of f. Should be used inside
   with-open to ensure the reader is properly closed."
-  [^String f]
-  (let [file (cio/file f)
-        path (.getAbsolutePath file)]
-    (-> (util/compressor-input-stream path)
-        cio/reader
-        (FASTQReader. path))))
+  [f]
+  (-> (util/compressor-input-stream f)
+      cio/reader
+      (FASTQReader. (util/as-url f))))
 
 (defn ^FASTQWriter writer
   "Returns an open cljam.io.fastq.FASTQWriter of f. Should be used inside
   with-open to ensure the writer is properly closed."
-  [^String f]
-  (let [file (cio/file f)
-        path (.getAbsolutePath file)]
-    (-> (util/compressor-output-stream path)
-        cio/writer
-        (FASTQWriter. path))))
+  [f]
+  (-> (util/compressor-output-stream f)
+      cio/writer
+      (FASTQWriter. (util/as-url f))))
 
 (defrecord FASTQRead [^String name ^String sequence quality])
 
diff --git a/src/cljam/io/protocols.clj b/src/cljam/io/protocols.clj
index 14fca24..2b5c786 100644
--- a/src/cljam/io/protocols.clj
+++ b/src/cljam/io/protocols.clj
@@ -9,16 +9,16 @@
 (defrecord SAMQuerynameBlock [data qname ^int flag])
 
 (defprotocol IReader
-  (reader-path [this]
-    "Returns the file's absolute path.")
+  (reader-url [this]
+    "Returns the file's URL.")
   (read [this] [this option]
     "Sequentially reads contents of the file.")
   (indexed? [this]
     "Returns true if the reader can be randomly accessed, false if not."))
 
 (defprotocol IWriter
-  (writer-path [this]
-    "Returns the file's absolute path."))
+  (writer-url [this]
+    "Returns the source's URL."))
 
 (defprotocol IRegionReader
   (read-in-region [this region] [this region option]
diff --git a/src/cljam/io/sam.clj b/src/cljam/io/sam.clj
index f5008ed..b904d07 100644
--- a/src/cljam/io/sam.clj
+++ b/src/cljam/io/sam.clj
@@ -36,17 +36,16 @@
 
 (defn ^Closeable reader
   "Selects suitable reader from f's extension, returning the reader. Opens a new
-  reader if given a path, clones the reader if given a reader. This function
-  supports SAM and BAM formats."
+  reader if the arg represents a file such as String path, java.io.File, or
+  java.net.URL. If a reader is given, clones the reader. This function supports
+  SAM and BAM formats."
   [f]
-  (if (string? f)
+  (if (io-util/bam-reader? f)
+    (clone-bam-reader f)
     (case (io-util/file-type f)
       :sam (sam-reader f)
       :bam (bam-reader f)
-      (throw (IllegalArgumentException. "Invalid file type")))
-    (cond
-      (io-util/bam-reader? f) (clone-bam-reader f)
-      :else (throw (IllegalArgumentException. "Invalid reader type")))))
+      (throw (IllegalArgumentException. "Invalid source type")))))
 
 (defn read-header
   "Returns header of the SAM/BAM file."
diff --git a/src/cljam/io/sam/reader.clj b/src/cljam/io/sam/reader.clj
index 93a5db6..d779cdc 100644
--- a/src/cljam/io/sam/reader.clj
+++ b/src/cljam/io/sam/reader.clj
@@ -4,7 +4,8 @@
             [cljam.io.sam.util :as sam-util]
             [cljam.io.sam.util.refs :as refs]
             [cljam.io.sam.util.header :as header]
-            [cljam.io.protocols :as protocols])
+            [cljam.io.protocols :as protocols]
+            [cljam.util :as util])
   (:import [java.io BufferedReader Closeable]
            [cljam.io.protocols SAMCoordinateBlock SAMQuerynameBlock]))
 
@@ -12,13 +13,13 @@
 
 ;;; reader
 
-(deftype SAMReader [f header reader]
+(deftype SAMReader [url header reader]
   Closeable
   (close [this]
     (.close ^Closeable (.reader this)))
   protocols/IReader
-  (reader-path [this]
-    (.f this))
+  (reader-url [this]
+    (.url this))
   (read [this]
     (protocols/read this {}))
   (read [this region]
@@ -112,5 +113,5 @@
 (defn reader [f]
   (let [header (with-open [r (cio/reader f)]
                  (read-header* r))]
-    (->SAMReader (.getAbsolutePath (cio/file f))
+    (->SAMReader (util/as-url f)
                  header (cio/reader f))))
diff --git a/src/cljam/io/sam/writer.clj b/src/cljam/io/sam/writer.clj
index 40a27be..d31a813 100644
--- a/src/cljam/io/sam/writer.clj
+++ b/src/cljam/io/sam/writer.clj
@@ -4,7 +4,8 @@
             [clojure.tools.logging :as logging]
             [cljam.io.sam.util :as sam-util]
             [cljam.io.sam.util.header :as header]
-            [cljam.io.protocols :as protocols])
+            [cljam.io.protocols :as protocols]
+            [cljam.util :as util])
   (:import [java.io BufferedWriter Closeable]))
 
 (declare write-header* write-alignments* write-blocks*)
@@ -12,13 +13,13 @@
 ;; SAMWriter
 ;; ---------
 
-(deftype SAMWriter [^BufferedWriter writer f]
+(deftype SAMWriter [^BufferedWriter writer url]
   Closeable
   (close [this]
     (.close writer))
   protocols/IWriter
-  (writer-path [this]
-    (.f this))
+  (writer-url [this]
+    (.url this))
   protocols/IAlignmentWriter
   (write-header [this header]
     (write-header* this header))
@@ -58,4 +59,4 @@
 (defn ^SAMWriter writer
   [f]
   (->SAMWriter (cio/writer f)
-               (.getAbsolutePath (cio/file f))))
+               (util/as-url f)))
diff --git a/src/cljam/io/sequence.clj b/src/cljam/io/sequence.clj
index 3e010c2..824cae3 100644
--- a/src/cljam/io/sequence.clj
+++ b/src/cljam/io/sequence.clj
@@ -31,18 +31,17 @@
 
 (defn ^Closeable reader
   "Selects suitable reader from f's extension, returning the open reader. Opens
-  a new reader if given a path, clones the reader if given a reader. This
-  function supports FASTA and TwoBit formats."
+  a new reader if the arg represents a file such as String path, java.io.File,
+  or java.net.URL. If a reader is given, clones the reader. This function
+  supports FASTA and TwoBit formats."
   [f]
-  (if (string? f)
-    (case (io-util/file-type f)
-      :fasta (fasta-reader f)
-      :2bit (twobit-reader f)
-      (throw (IllegalArgumentException. "Invalid file type")))
-    (cond
-      (io-util/fasta-reader? f) (fa-core/clone-reader f)
-      (io-util/twobit-reader? f) (tb-reader/clone-reader f)
-      :else (throw (IllegalArgumentException. "Invalid reader type")))))
+  (cond
+    (io-util/fasta-reader? f) (fa-core/clone-reader f)
+    (io-util/twobit-reader? f) (tb-reader/clone-reader f)
+    :else (case (io-util/file-type f)
+            :fasta (fasta-reader f)
+            :2bit (twobit-reader f)
+            (throw (IllegalArgumentException. "Invalid file type")))))
 
 (defn read-sequence
   "Reads sequence in region of FASTA/TwoBit file."
diff --git a/src/cljam/io/tabix.clj b/src/cljam/io/tabix.clj
index 2c25263..c48a620 100644
--- a/src/cljam/io/tabix.clj
+++ b/src/cljam/io/tabix.clj
@@ -1,11 +1,10 @@
 (ns cljam.io.tabix
   "Alpha - subject to change.
   Reader of a TABIX format file."
-  (:require [clojure.java.io :as cio]
+  (:require [cljam.io.util.bgzf :as bgzf]
             [cljam.io.util.lsb :as lsb])
   (:import java.util.Arrays
-           [java.io DataInputStream IOException]
-           bgzf4j.BGZFInputStream))
+           [java.io DataInputStream IOException]))
 
 (def tabix-magic "TBI\1")
 
@@ -64,5 +63,5 @@
 
 (defn read-index
   [f]
-  (with-open [r (DataInputStream. (BGZFInputStream. (cio/file f)))]
+  (with-open [r (DataInputStream. (bgzf/bgzf-input-stream f))]
     (read-index* r)))
diff --git a/src/cljam/io/twobit/reader.clj b/src/cljam/io/twobit/reader.clj
index 39678c1..a0d2112 100644
--- a/src/cljam/io/twobit/reader.clj
+++ b/src/cljam/io/twobit/reader.clj
@@ -1,11 +1,13 @@
 (ns cljam.io.twobit.reader
   (:require [clojure.java.io :as cio]
             [cljam.io.protocols :as protocols]
-            [cljam.io.util.lsb :as lsb])
+            [cljam.io.util.lsb :as lsb]
+            [cljam.util :as util])
   (:import [java.io Closeable DataInput RandomAccessFile]
+           [java.net URL]
            [java.nio CharBuffer]))
 
-(defrecord TwoBitReader [^RandomAccessFile reader ^String f endian file-index seq-index]
+(defrecord TwoBitReader [^RandomAccessFile reader ^URL url endian file-index seq-index]
   Closeable
   (close [this]
     (.close ^Closeable (.reader this))))
@@ -145,7 +147,7 @@
 
 (extend-type TwoBitReader
   protocols/IReader
-  (reader-path [this] (.f this))
+  (reader-url [this] (.url this))
   (read
     ([this] (protocols/read this {}))
     ([this option] (protocols/read-all-sequences this option)))
@@ -183,11 +185,11 @@
                                    (.seek raf offset)
                                    (read-sequence-header! raf endian))))
                               indices)]
-        (TwoBitReader. rdr abs-f endian indices seq-indices)))))
+        (TwoBitReader. rdr (util/as-url abs-f) endian indices seq-indices)))))
 
 (defn ^TwoBitReader clone-reader
   "Clones .2bit reader sharing persistent objects."
   [^TwoBitReader rdr]
-  (let [f (.f rdr)
-        raf (RandomAccessFile. ^String f "r")]
-    (TwoBitReader. raf f (.endian rdr) (.file-index rdr) (.seq-index rdr))))
+  (let [url (.url rdr)
+        raf (RandomAccessFile. (cio/as-file url) "r")]
+    (TwoBitReader. raf url (.endian rdr) (.file-index rdr) (.seq-index rdr))))
diff --git a/src/cljam/io/twobit/writer.clj b/src/cljam/io/twobit/writer.clj
index 9c15e33..3771461 100644
--- a/src/cljam/io/twobit/writer.clj
+++ b/src/cljam/io/twobit/writer.clj
@@ -1,19 +1,20 @@
 (ns cljam.io.twobit.writer
   (:require [clojure.java.io :as cio]
             [cljam.io.protocols :as protocols]
-            [cljam.io.util.lsb :as lsb])
+            [cljam.io.util.lsb :as lsb]
+            [cljam.util :as util])
   (:import [java.io Closeable OutputStream DataOutputStream BufferedOutputStream FileOutputStream]
            [java.nio ByteBuffer]))
 
 (declare write-sequences)
 
-(deftype TwoBitWriter [f writer file-output-stream index]
+(deftype TwoBitWriter [url writer file-output-stream index]
   Closeable
   (close [this]
     (.close ^Closeable (.writer this)))
   protocols/IWriter
-  (writer-path [this]
-    (.f this))
+  (writer-url [this]
+    (.url this))
   protocols/ISequenceWriter
   (write-sequences [this seqs]
     (write-sequences this seqs)))
@@ -25,7 +26,7 @@
         fos (FileOutputStream. abs-f)
         bos (BufferedOutputStream. fos)
         dos (DataOutputStream. bos)]
-    (TwoBitWriter. abs-f dos fos index)))
+    (TwoBitWriter. (util/as-url abs-f) dos fos index)))
 
 (defn- write-file-header!
   "Writes a 2bit file header. Supports little-endian only."
diff --git a/src/cljam/io/util.clj b/src/cljam/io/util.clj
index 9db3323..40d6a7e 100644
--- a/src/cljam/io/util.clj
+++ b/src/cljam/io/util.clj
@@ -14,7 +14,8 @@
             cljam.io.twobit.reader
             cljam.io.twobit.writer
             cljam.io.fastq
-            cljam.io.bed))
+            cljam.io.bed
+            [cljam.util :as util]))
 
 (defn alignment-reader?
   "Checks if given object implements protocol IAlignmentReader."
@@ -127,10 +128,10 @@
   (instance? cljam.io.bed.BEDWriter wtr))
 
 (defn file-type
-  "Detects a file format from path string f, returning a keyword representing
-  the format. Throws an exception if an unsupported file is supplied."
+  "Detects a file format from a path of f, returning a keyword representing the
+  format. Throws an exception if an unsupported file is supplied."
   [f]
-  (condp re-find f
+  (condp re-find (.getPath (util/as-url f))
     #"(?i)\.sam$" :sam
     #"(?i)\.bai$" :bai
     #"(?i)\.bam$" :bam
diff --git a/src/cljam/io/util/bgzf.clj b/src/cljam/io/util/bgzf.clj
index b81ab11..550d511 100644
--- a/src/cljam/io/util/bgzf.clj
+++ b/src/cljam/io/util/bgzf.clj
@@ -1,5 +1,54 @@
 (ns cljam.io.util.bgzf
-  (:refer-clojure :exclude [compare]))
+  (:refer-clojure :exclude [compare])
+  (:require [clojure.java.io :as cio])
+  (:import [java.io File]
+           [java.net MalformedURLException URI URL]
+           [bgzf4j BGZFInputStream BGZFOutputStream]))
+
+(defprotocol BGZFIOFactory
+  (make-bgzf-input-stream [x])
+  (make-bgzf-output-stream [x]))
+
+(extend-protocol BGZFIOFactory
+  File
+  (make-bgzf-input-stream [^File x]
+    (BGZFInputStream. x))
+  (make-bgzf-output-stream [^File x]
+    (BGZFOutputStream. x))
+
+  URL
+  (make-bgzf-input-stream [^URL x]
+    (if (= (.getProtocol x) "file")
+      (make-bgzf-input-stream (cio/as-file x))
+      (BGZFInputStream. x)))
+  (make-bgzf-output-stream [^URL x]
+    (if (= (.getProtocol x) "file")
+      (make-bgzf-output-stream (cio/as-file x))
+      (throw (IllegalArgumentException. (str "Can not write to non-file URL <" x ">")))))
+
+  URI
+  (make-bgzf-input-stream [^URI x]
+    (make-bgzf-input-stream (.toURL x)))
+  (make-bgzf-output-stream [^URI x]
+    (make-bgzf-output-stream (.toURL x)))
+
+  String
+  (make-bgzf-input-stream [x]
+    (try
+      (make-bgzf-input-stream (URL. x))
+      (catch MalformedURLException _
+        (make-bgzf-input-stream (cio/as-file x)))))
+  (make-bgzf-output-stream [x]
+    (try
+      (make-bgzf-output-stream (URL. x))
+      (catch MalformedURLException _
+        (make-bgzf-output-stream (cio/as-file x))))))
+
+(defn ^BGZFInputStream bgzf-input-stream [x]
+  (make-bgzf-input-stream x))
+
+(defn ^BGZFOutputStream bgzf-output-stream [x]
+  (make-bgzf-output-stream x))
 
 (def ^:private ^:const shift-amount 16)
 
diff --git a/src/cljam/io/vcf.clj b/src/cljam/io/vcf.clj
index 238eade..8554785 100644
--- a/src/cljam/io/vcf.clj
+++ b/src/cljam/io/vcf.clj
@@ -27,7 +27,7 @@
                     (vcf-reader/load-meta-info r))
         header (with-open [r (cio/reader (util/compressor-input-stream f))]
                  (vcf-reader/load-header r))]
-    (VCFReader. (.getAbsolutePath (cio/file f)) meta-info header
+    (VCFReader. (util/as-url f) meta-info header
                 (cio/reader (util/compressor-input-stream f)))))
 
 (defn ^BCFReader bcf-reader
@@ -81,7 +81,7 @@
                                 [\"CHROM\" \"POS\" \"ID\" \"REF\" \"ALT\" ...])]
       (WRITING-VCF))"
   [f meta-info header]
-  (doto (VCFWriter. (.getAbsolutePath (cio/file f))
+  (doto (VCFWriter. (util/as-url f)
                     (cio/writer (util/compressor-output-stream f))
                     meta-info
                     header)
diff --git a/src/cljam/io/vcf/reader.clj b/src/cljam/io/vcf/reader.clj
index 6303abb..6655c91 100644
--- a/src/cljam/io/vcf/reader.clj
+++ b/src/cljam/io/vcf/reader.clj
@@ -15,12 +15,12 @@
 
 (declare read-variants)
 
-(deftype VCFReader [f meta-info header reader]
+(deftype VCFReader [url meta-info header reader]
   Closeable
   (close [this]
     (.close ^Closeable (.reader this)))
   protocols/IReader
-  (reader-path [this] (.f this))
+  (reader-url [this] (.url this))
   (read [this] (read-variants this))
   (read [this option] (read-variants this option))
   (indexed? [_] false)
diff --git a/src/cljam/io/vcf/writer.clj b/src/cljam/io/vcf/writer.clj
index eac125f..f79fc01 100644
--- a/src/cljam/io/vcf/writer.clj
+++ b/src/cljam/io/vcf/writer.clj
@@ -12,12 +12,12 @@
 ;; VCFWriter
 ;; ---------
 
-(deftype VCFWriter [f writer meta-info header]
+(deftype VCFWriter [url writer meta-info header]
   Closeable
   (close [this]
     (.close ^Closeable (.writer this)))
   protocols/IWriter
-  (writer-path [this] (.f this))
+  (writer-url [this] (.url this))
   protocols/IVariantWriter
   (write-variants [this variants]
     (write-variants this variants)))
diff --git a/src/cljam/util.clj b/src/cljam/util.clj
index a188e36..f1e37c4 100644
--- a/src/cljam/util.clj
+++ b/src/cljam/util.clj
@@ -1,9 +1,8 @@
 (ns cljam.util
   "General utilities."
-  (:require [clojure.java.io :refer [file] :as cio]
-            [clojure.string :as cstr]
-            [proton.core :as proton])
-  (:import [org.apache.commons.compress.compressors
+  (:require [clojure.java.io :refer [file] :as cio])
+  (:import [java.net MalformedURLException URL]
+           [org.apache.commons.compress.compressors
             CompressorStreamFactory CompressorException]))
 
 ;; Disk cache
@@ -48,10 +47,17 @@
 ;; file utils
 ;; ---------
 
+(defn ^URL as-url
+  [x]
+  (try
+    (cio/as-url x)
+    (catch MalformedURLException _
+      (cio/as-url (cio/file x)))))
+
 (defn basename
-  [path]
-  (let [filename (.getName (file path))]
-    (first (cstr/split filename #"\.(?=[^\.]+$)"))))
+  [x]
+  (when-let [url (as-url x)]
+    (second (re-find #"([^/]+)\.(?=[^\./]+$)" (.getPath url)))))
 
 (def ^:private compressor-map
   {:gzip CompressorStreamFactory/GZIP
@@ -77,7 +83,7 @@
   compressor type is not known. Should be used inside with-open to ensure the
   OutputStream is properly closed."
   ([f]
-   (compressor-output-stream f (condp re-find (.getName (file f))
+   (compressor-output-stream f (condp re-find (.getPath (as-url f))
                                  #"(?i)\.(gz|gzip)$" :gzip
                                  #"(?i)\.(bz2|bzip2)$" :bzip2
                                  nil)))


Test patch:
diff --git a/test/cljam/io/bam_index_test.clj b/test/cljam/io/bam_index_test.clj
index 8f1fc6a..505e8e0 100644
--- a/test/cljam/io/bam_index_test.clj
+++ b/test/cljam/io/bam_index_test.clj
@@ -1,6 +1,7 @@
 (ns cljam.io.bam-index-test
   "Tests for cljam.io.bam-index."
-  (:require [clojure.test :refer :all]
+  (:require [clojure.java.io :as cio]
+            [clojure.test :refer :all]
             [cljam.test-common :refer :all]
             [cljam.io.bam-index :as bai]))
 
@@ -54,3 +55,13 @@
 (deftest-remote linear-index-is-done-without-errors-with-a-large-file
   (with-before-after {:before (prepare-cavia!)}
     (is (not-throw? (bai/linear-index test-large-bai-file 0)))))
+
+(deftest source-type-test
+  (with-open [server (http-server)]
+    (are [x] (and (vector? (bai/bin-index x 0))
+                  (vector? (bai/linear-index x 0))
+                  (= (bai/get-spans (bai/bam-index x) 0 0 100) '((97 555))))
+      test-bai-file
+      (cio/file test-bai-file)
+      (cio/as-url (cio/file test-bai-file))
+      (cio/as-url (str (:uri server) "/bam/test.sorted.bam.bai")))))
diff --git a/test/cljam/io/bed_test.clj b/test/cljam/io/bed_test.clj
index a021ae7..58b6297 100644
--- a/test/cljam/io/bed_test.clj
+++ b/test/cljam/io/bed_test.clj
@@ -1,5 +1,6 @@
 (ns cljam.io.bed-test
-  (:require [clojure.test :refer :all]
+  (:require [clojure.java.io :as cio]
+            [clojure.test :refer :all]
             [cljam.test-common :refer :all]
             [cljam.io.bed :as bed]
             [cljam.io.sam :as sam]
@@ -337,3 +338,28 @@
                (bed/read-fields rdr2)))
         (is (= xs
                (bed/read-fields rdr3)))))))
+
+(deftest source-type-test
+  (testing "reader"
+    (with-open [server (http-server)]
+      (are [x] (with-open [rdr (bed/reader x)]
+                 (= (bed/read-fields rdr)
+                    [{:chr "chr22" :start 1001 :end 5000 :name "cloneA" :score 960 :strand :plus :thick-start 1001
+                      :thick-end 5000 :item-rgb "0" :block-count 2 :block-sizes [567 488] :block-starts [0 3512]}
+                     {:chr "chr22" :start 2001 :end 6000 :name "cloneB" :score 900 :strand :minus :thick-start 2001
+                      :thick-end 6000 :item-rgb "0" :block-count 2 :block-sizes [433 399] :block-starts [0 3601]}]))
+        test-bed-file1
+        (cio/file test-bed-file1)
+        (cio/as-url (cio/file test-bed-file1))
+        (cio/as-url (str (:uri server) "/bed/test1.bed")))))
+
+  (testing "writer"
+    (let [temp-file (str temp-dir "/test1.bed")]
+      (are [x] (with-before-after {:before (prepare-cache!)
+                                   :after (clean-cache!)}
+                 (with-open [rdr (bed/reader test-bed-file1)
+                             wtr (bed/writer temp-file)]
+                   (not-throw? (bed/write-fields wtr (bed/read-fields rdr)))))
+        temp-file
+        (cio/file temp-file)
+        (cio/as-url (cio/file temp-file))))))
diff --git a/test/cljam/io/fastq_test.clj b/test/cljam/io/fastq_test.clj
index 7fb0199..fe1fffd 100644
--- a/test/cljam/io/fastq_test.clj
+++ b/test/cljam/io/fastq_test.clj
@@ -124,3 +124,23 @@
   (is (nil? (fq/deserialize-name "@EAS139:136:FC706VJ:2:2104:15343:197393_1:Y:18:ATCACG")))
 
   (is (nil? (fq/deserialize-name "@HWUSI-EAS100R:6:73:941:1973#N/1"))))
+
+(deftest source-type-test
+  (testing "reader"
+    (with-open [server (http-server)]
+      (are [x] (with-open [rdr (fq/reader x)]
+                 (= (map (partial into {}) (fq/read-sequences rdr)) test-fq-sequences))
+        test-fq-file
+        (cio/file test-fq-file)
+        (cio/as-url (cio/file test-fq-file))
+        (cio/as-url (str (:uri server) "/fastq/test.fq")))))
+
+  (testing "writer"
+    (let [temp-file (str temp-dir "test.fq")]
+      (are [x] (with-before-after {:before (prepare-cache!)
+                                   :after (clean-cache!)}
+                 (with-open [wtr (fq/writer x)]
+                   (not-throw? (fq/write-sequences wtr test-fq-sequences))))
+        temp-file
+        (cio/file temp-file)
+        (cio/as-url (cio/file temp-file))))))
diff --git a/test/cljam/io/sam_test.clj b/test/cljam/io/sam_test.clj
index 9e6b523..7680dda 100644
--- a/test/cljam/io/sam_test.clj
+++ b/test/cljam/io/sam_test.clj
@@ -288,3 +288,33 @@
      (= (with-open [r (sam/reader opts-bam-file)]
           (->> (sam/read-alignments r) (mapcat :options) (map bytes-to-seq) doall))
         test-options))))
+
+(deftest source-type-test
+  (testing "reader"
+    (with-open [server (http-server)]
+      (are [x] (with-open [rdr (sam/reader x)]
+                 (and (= (sam/read-refs rdr) test-sam-refs)
+                      (= (sam/read-alignments rdr) (:alignments test-sam))))
+        test-sam-file
+        (cio/file test-sam-file)
+        (cio/as-url (cio/file test-sam-file))
+        (cio/as-url (str (:uri server) "/sam/test.sam"))
+
+        test-bam-file
+        (cio/file test-bam-file)
+        (cio/as-url (cio/file test-bam-file))
+        (cio/as-url (str (:uri server) "/bam/test.bam")))))
+
+  (testing "writer"
+    (are [x] (with-before-after {:before (prepare-cache!)
+                                 :after (clean-cache!)}
+               (with-open [wtr (sam/writer x)]
+                 (and (not-throw? (sam/write-header wtr (:header test-sam)))
+                      (not-throw? (sam/write-alignments wtr (:alignments test-sam) nil)))))
+      temp-sam-file
+      (cio/file temp-sam-file)
+      (cio/as-url (cio/file temp-sam-file))
+
+      temp-bam-file
+      (cio/file temp-bam-file)
+      (cio/as-url (cio/file temp-bam-file)))))
diff --git a/test/cljam/io/sequence_test.clj b/test/cljam/io/sequence_test.clj
index ba86ad9..dd668d0 100644
--- a/test/cljam/io/sequence_test.clj
+++ b/test/cljam/io/sequence_test.clj
@@ -277,3 +277,38 @@
              {:name "SEQ5" :sequence "AAAAGCTA"}]]
       (with-open [w (cseq/writer f)] (cseq/write-sequences w s))
       (with-open [r (cseq/reader f)] (is (= (cseq/read-all-sequences r) s))))))
+
+(deftest source-type-test
+  (testing "reader"
+    (are [x] (with-open [rdr (cseq/reader x)]
+               (= (cseq/read-sequence rdr {:chr "ref" :start 44 :end 45} {}) "AT"))
+      test-fa-file
+      (cio/file test-fa-file)
+      (cio/as-url (cio/file test-fa-file))
+
+      test-twobit-file
+      (cio/file test-twobit-file)
+      (cio/as-url (cio/file test-twobit-file))))
+
+  (testing "reader (non-file URL is not supported)"
+    (with-open [server (http-server)]
+      (are [x] (thrown? Exception
+                        (with-open [rdr (cseq/reader x)]
+                          (cseq/read-sequence rdr {:chr "ref" :start 44 :end 45} {})))
+        (cio/as-url (str (:uri server) "/fasta/test.fa"))
+        (cio/as-url (str (:uri server) "/twobit/test.2bit")))))
+
+  (testing "writer"
+    (let [temp-test-twobit-file (str temp-dir "test.2bit")]
+      (are [x] (with-before-after {:before (prepare-cache!)
+                                   :after (clean-cache!)}
+                 (with-open [rdr (cseq/reader test-fa-file)
+                             wtr (cseq/writer x)]
+                   (not-throw? (cseq/write-sequences wtr (cseq/read-all-sequences rdr)))))
+        temp-test-fa-file
+        (cio/file temp-test-fa-file)
+        (cio/as-url (cio/file temp-test-fa-file))
+
+        temp-test-twobit-file
+        (cio/file temp-test-twobit-file)
+        (cio/as-url (cio/file temp-test-twobit-file))))))
diff --git a/test/cljam/io/tabix_test.clj b/test/cljam/io/tabix_test.clj
index 20573cd..bdd6488 100644
--- a/test/cljam/io/tabix_test.clj
+++ b/test/cljam/io/tabix_test.clj
@@ -1,5 +1,6 @@
 (ns cljam.io.tabix-test
-  (:require [clojure.test :refer :all]
+  (:require [clojure.java.io :as cio]
+            [clojure.test :refer :all]
             [cljam.test-common :refer :all]
             [cljam.io.tabix :as tbi]))
 
@@ -25,3 +26,11 @@
 (deftest-remote large-file
   (with-before-after {:before (prepare-cavia!)}
     (is (not-throw? (tbi/read-index test-large-tabix-file)))))
+
+(deftest source-type-test
+  (with-open [server (http-server)]
+    (are [x] (map? (tbi/read-index x))
+      test-tabix-file
+      (cio/file test-tabix-file)
+      (cio/as-url (cio/file test-tabix-file))
+      (cio/as-url (str (:uri server) "/tabix/test.gtf.gz.tbi")))))
diff --git a/test/cljam/io/vcf_test.clj b/test/cljam/io/vcf_test.clj
index 57061b7..dc2ee8b 100644
--- a/test/cljam/io/vcf_test.clj
+++ b/test/cljam/io/vcf_test.clj
@@ -7,6 +7,7 @@
   (:import bgzf4j.BGZFException))
 
 (def ^:private temp-file (str temp-dir "/test.vcf"))
+(def ^:private temp-bcf-file (str temp-dir "/test.bcf"))
 
 (defn- slurp-vcf-for-test
   [f & [depth]]
@@ -195,3 +196,34 @@
         (is (= (vcf/meta-info r2) (vcf/meta-info r1)))
         (doseq [[x2 x1] (map vector (vcf/read-variants r2) (vcf/read-variants r1))]
           (is (= x2 x1)))))))
+
+(deftest source-type-test
+  (testing "reader"
+    (with-open [server (http-server)]
+      (are [x] (with-open [rdr (vcf/reader x)]
+                 (and (= (vcf/meta-info rdr) test-vcf-v4_3-meta-info)
+                      (= (vcf/header rdr) test-vcf-v4_3-header)
+                      (= (vcf/read-variants rdr) test-vcf-v4_3-variants-deep)))
+        test-vcf-v4_3-file
+        (cio/file test-vcf-v4_3-file)
+        (cio/as-url (cio/file test-vcf-v4_3-file))
+        (cio/as-url (str (:uri server) "/vcf/test-v4_3.vcf"))
+
+        test-bcf-v4_3-file
+        (cio/file test-bcf-v4_3-file)
+        (cio/as-url (cio/file test-bcf-v4_3-file))
+        (cio/as-url (str (:uri server) "/bcf/test-v4_3.bcf")))))
+  (testing "writer"
+    (are [x] (with-before-after {:before (prepare-cache!)
+                                 :after (clean-cache!)}
+               (not-throw? (spit-vcf-for-test x
+                                              test-vcf-v4_3-meta-info
+                                              test-vcf-v4_3-header
+                                              test-vcf-v4_3-variants-deep)))
+      temp-file
+      (cio/file temp-file)
+      (cio/as-url (cio/file temp-file))
+
+      temp-bcf-file
+      (cio/file temp-bcf-file)
+      (cio/as-url (cio/file temp-bcf-file)))))
diff --git a/test/cljam/test_common.clj b/test/cljam/test_common.clj
index e4dd7d2..3470529 100644
--- a/test/cljam/test_common.clj
+++ b/test/cljam/test_common.clj
@@ -1,12 +1,15 @@
 (ns cljam.test-common
   (:require [digest]
-            [clojure.java.io :refer [file]]
+            [clojure.java.io :refer [file] :as cio]
             [clojure.tools.logging :refer [*logger-factory*]]
             [clojure.tools.logging.impl :refer [disabled-logger-factory]]
+            [stub-http.core :as http]
             [cljam.io.protocols :as protocols]
             [cljam.io.sam :as sam]
             [cljam.io.sequence :as cseq]
-            [cavia.core :as cavia :refer [defprofile with-profile]]))
+            [cavia.core :as cavia :refer [defprofile with-profile]])
+  (:import java.io.File
+           stub_http.core.NanoFakeServer))
 
 (defn- _in-cloverage? []
   (try
@@ -669,3 +672,27 @@
      :FORMAT [:GT :GQ :DP :HQ], :NA00001 {:GT "0|0", :GQ 54, :DP 7, :HQ [56 60]}, :NA00002 {:GT "0|0", :GQ 48, :DP 4, :HQ [51 51]}, :NA00003 {:GT "0/0", :GQ 61, :DP 2}}
     {:chr "20", :pos 1234567, :id "microsat1", :ref "GTC", :alt ["G" "GTCT"], :qual 50.0, :filter [:PASS], :info {:NS 3, :DP 9, :AA "G"},
      :FORMAT [:GT :GQ :DP], :NA00001 {:GT "0/1", :GQ 35, :DP 4}, :NA00002 {:GT "0/2", :GQ 17, :DP 2}, :NA00003 {:GT "1/1", :GQ 40, :DP 3}}))
+
+;; http server
+
+(defn- slurp-bytes [x]
+  (with-open [in (cio/input-stream x)
+              out (java.io.ByteArrayOutputStream.)]
+    (cio/copy in out)
+    (.toByteArray out)))
+
+(defn ^NanoFakeServer http-server []
+  (let [gen-route? (fn [^File f]
+                     (< (.length f) (* 1024 1024)))
+        gen-route (fn [^File f]
+                    (when (.isFile f)
+                      (let [[_ path] (re-matches #"test-resources(/.+)" (.getPath f))]
+                        [path {:status 200
+                               :content-type "application/octet-stream;charset=ISO-8859-1"
+                               :body (String. ^bytes (slurp-bytes f) "ISO-8859-1")}])))
+        routes (->> (file "test-resources")
+                    file-seq
+                    (filter gen-route?)
+                    (keep gen-route)
+                    (into {}))]
+    (http/start! routes)))
diff --git a/test/cljam/util_test.clj b/test/cljam/util_test.clj
index f316aa2..5f775f5 100644
--- a/test/cljam/util_test.clj
+++ b/test/cljam/util_test.clj
@@ -1,7 +1,9 @@
 (ns cljam.util-test
   "Tests for cljam.util."
-  (:require [clojure.test :refer :all]
-            [cljam.util :as util]))
+  (:require [clojure.java.io :as cio]
+            [clojure.test :refer :all]
+            [cljam.util :as util])
+  (:import [java.net URL]))
 
 (deftest ubyte
   (are [?n ?expected] (= (util/ubyte ?n) ?expected)
@@ -12,3 +14,36 @@
   (are [?n] (thrown? AssertionError (util/ubyte ?n))
     -1
     256))
+
+(deftest as-url-test
+  (are [x] (instance? URL (util/as-url x))
+    (URL. "http://example.com/path/to/file.bam")
+    (URL. "file:/path/to/file.bam")
+
+    (cio/file "/path/to/file.bam")
+    (cio/file "path/to/file.bam")
+
+    "http://example.com/path/to/file.bam"
+    "file:/path/to/file.bam"
+    "/path/to/file.bam"
+    "path/to/file.bam"
+    "")
+  (is (nil? (util/as-url nil))))
+
+(deftest basename-test
+  (are [x e] (= (util/basename x) e)
+    (URL. "http://example.com/path/to/file.bam") "file"
+    (URL. "file:/path/to/file.bam")              "file"
+
+    (cio/file "/path/to/file.bam") "file"
+    (cio/file "path/to/file.bam")  "file"
+
+    "http://example.com/path/to/file.bam" "file"
+    "file:/path/to/file.bam"              "file"
+    "/path/to/file.bam"                   "file"
+    "path/to/file.bam"                    "file"
+
+    (URL. "file:/path/to/file.sorted.bam") "file.sorted"
+
+    ""  nil
+    nil nil))