verm-client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3a616e96483beae73fd10062005d641d30fb5352
4
+ data.tar.gz: 6b9fffb843d7d12a75c7cdb3658b193a740e5f32
5
+ SHA512:
6
+ metadata.gz: 0aa23231f3b808815b44a89bf9cd81adf3e919affded5249c58ed6486aa41c4891f6d02427672c87b90e760047aed62b5033ec2dd8f1c9efe922c27d15997793
7
+ data.tar.gz: 9a333cbd52d2fc22b4e9389f0bb49f44592ffc26388bff151cb0795c9b54da62c89fd760f4556dbacb83ab57b0a3fedfdf974f1082bf75ffffe9bbd4ecf2ea4a
data/.gitignore ADDED
@@ -0,0 +1,34 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ # Gemfile.lock
30
+ # .ruby-version
31
+ # .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2015 Will Bryant, Sekuda Ltd
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
20
+
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+ Verm client library for Ruby
2
+ ============================
3
+
4
+ Verm (https://github.com/willbryant/verm) is a WORM (write-once, read-many) file store to make it easy to reliably store and replicate files. Clients talk to Verm over simple HTTP POST and GET calls.
5
+
6
+ This tiny client library adds one-line convenience methods for storing files in Verm and retrieving/streaming them back again.
7
+
8
+
9
+ Saving content
10
+ --------------
11
+
12
+ ```ruby
13
+ mydata = "data to store"
14
+ VERM_CLIENT = Verm::Client.new("my-verm-server")
15
+ VERM_CLIENT.store("/important_gifs/january", mydata, "text/plain") # => String
16
+ ```
17
+
18
+ The `store` call will return a path like `/important_gifs/january/ab/cdefgh.txt`, only longer (Verm makes the filename using an URL-safe base64 encoding of the SHA-256 of the content).
19
+
20
+
21
+ Saving files
22
+ ------------
23
+
24
+ You don't need to read the whole file into memory before calling store, just pass the IO object in:
25
+
26
+ ```ruby
27
+ VERM_CLIENT = Verm::Client.new("my-verm-server")
28
+
29
+ File.open("example.jpg", "rb") do |f|
30
+ VERM_CLIENT.store("/important_gifs/january", f, "image/jpeg") # => String
31
+ end
32
+ ```
33
+
34
+
35
+ Retrieving content
36
+ ------------------
37
+
38
+ ```ruby
39
+ VERM_CLIENT = Verm::Client.new("my-verm-server")
40
+
41
+ file, content_type = VERM_CLIENT.load("/important_gifs/january/ab/cdefgh.jpg")
42
+ ```
43
+
44
+ This basically does the same thing as the built-in Net::HTTP get method (aside from timeouts, error handling, etc.):
45
+
46
+ ```ruby
47
+ Net::HTTP.get('my-verm-server:3404', '/important_gifs/january/ab/cdefgh.jpg') # => String - the file content
48
+ ```
49
+
50
+ So you really don't need a client library for this - but it's usually more convenient to configure the Verm server somewhere central, instead of sprinkling it through your application.
51
+
52
+ Verm stores all content without touching its character encoding, but Ruby 1.9+ treats all strings as having a specific character encoding. Most applications use UTF-8 for all text, so by default, the `load` method will set the character encoding of `text/*` content returned by Verm to `UTF-8`. You can override this or disable it entirely:
53
+
54
+ ```ruby
55
+ file, content_type = VERM_CLIENT.load("/important_gifs/january/ab/cdefgh.csv", force_encoding: 'ISO-8859-1')
56
+ file, content_type = VERM_CLIENT.load("/important_gifs/january/ab/cdefgh.csv", force_encoding: nil)
57
+ ```
58
+
59
+
60
+ Retrieving large content
61
+ ------------------------
62
+
63
+ If the data is large, reading it all into memory as a string is inefficient. Instead you can use `stream`, which will yield the file contents in chunks:
64
+
65
+ ```ruby
66
+ VERM_CLIENT = Verm::Client.new("my-verm-server")
67
+
68
+ File.open("out.csv", "w") do |f|
69
+ VERM_CLIENT.stream("/big_data/ab/cdefgh.csv") do |chunk|
70
+ f.write chunk
71
+ end
72
+ end
73
+ ```
74
+
75
+ Because the chunks may not be broken on a character encoding boundary, the character encoding can't be set as for `load`, so they will still have the default `ASCII-8BIT` binary encoding.
76
+
77
+
78
+ File compression
79
+ ----------------
80
+
81
+ Like HTTP and web browsers, Verm treats gzip compression as being a way of making transfers smaller and faster, not a separate file type. So if you want to store gzipped files, you still get to say what content-type the actual file content has.
82
+
83
+ Saving files that are already gzip-compressed just requires one extra argument:
84
+
85
+ ```ruby
86
+ File.open("acme_20150101.csv.gz", "rb") do |f|
87
+ VERM_CLIENT.store("/third_party_files/acme/2015", f, "text/csv", encoding: "gzip")
88
+ end
89
+ ```
90
+
91
+ It's important to note that Ruby's Net::HTTP client will automatically uncompress gzip transfer-encoding on responses - so when you use `load` or `store`, you'll get the uncompressed content back, not the gzip file.
92
+
93
+ You can suppress that behavior by passing `Accept-Encoding: gzip` as a header (Net::HTTP would accept gzip anyway, but setting this header has the side effect of disabling uncompression):
94
+
95
+ ```ruby
96
+ file, content_type = VERM_CLIENT.load("/third_party_files/acme/2015/ab/cdefgh.csv", 'Accept-Encoding' => 'gzip')
97
+ ```
98
+
99
+
100
+ Network stuff
101
+ -------------
102
+
103
+ Verm runs its HTTP server on port 3404 by default. You can override this in the client configuration:
104
+
105
+ ```ruby
106
+ VERM_CLIENT = Verm::Client.new("my-verm-server", port: 80)
107
+ ```
108
+
109
+ Verm::Client defaults to a 15s timeout, which you can override:
110
+
111
+ ```ruby
112
+ VERM_CLIENT = Verm::Client.new("my-verm-server", timeout: 60)
113
+ ```
114
+
115
+ Like Net::HTTP, this timeout applies to individual network operations, not the whole method.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.pattern = "test/*_test.rb"
6
+ end
7
+
8
+ task :default => :test
data/lib/verm.rb ADDED
@@ -0,0 +1 @@
1
+ require 'verm/client'
@@ -0,0 +1,59 @@
1
+ require 'net/http'
2
+
3
+ module Verm
4
+ class Client
5
+ attr_reader :http_client
6
+
7
+ def initialize(hostname, port: 3404, timeout: 15)
8
+ @http_client = Net::HTTP.new(hostname, port)
9
+ @http_client.open_timeout = timeout
10
+ @http_client.read_timeout = timeout
11
+ @http_client.ssl_timeout = timeout
12
+ end
13
+
14
+ def store(directory, io_or_data, content_type, encoding: nil)
15
+ directory = "/#{directory}" unless directory[0] == '/'
16
+ request = Net::HTTP::Post.new(directory, 'Content-Type' => content_type)
17
+ request['Content-Encoding'] = encoding.to_s if encoding
18
+
19
+ if io_or_data.respond_to?(:read)
20
+ request.body_stream = io_or_data
21
+ if io_or_data.respond_to?(:size)
22
+ request['Content-Length'] = io_or_data.size
23
+ else
24
+ request['Transfer-Encoding'] = 'chunked'
25
+ end
26
+
27
+ io_or_data.rewind if io_or_data.respond_to?(:rewind)
28
+ response = http_client.request(request)
29
+ else
30
+ response = http_client.request(request, io_or_data)
31
+ end
32
+
33
+ response.error! unless response.is_a?(Net::HTTPSuccess)
34
+ raise "Got a HTTP #{response.code} when trying to store content - should always be 201" unless response.code.to_i == 201
35
+ raise "No location was returned when trying to store content - should always be given" unless response['location']
36
+ response['location']
37
+ end
38
+
39
+ def load(path, initheader = {}, force_text_encoding: "UTF-8")
40
+ response = http_client.request_get(path, initheader)
41
+ response.error! unless response.is_a?(Net::HTTPSuccess)
42
+
43
+ if force_text_encoding && response.content_type =~ /text\//
44
+ [response.body.force_encoding(force_text_encoding), response.content_type]
45
+ else
46
+ [response.body, response.content_type]
47
+ end
48
+ end
49
+
50
+ def stream(path, initheader = {})
51
+ http_client.request_get(path, initheader) do |response|
52
+ response.error! unless response.is_a?(Net::HTTPSuccess)
53
+ response.read_body do |chunk|
54
+ yield chunk
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ module Verm
2
+ VERSION = '1.0.0'
3
+ end
Binary file
Binary file
data/test/verm_test.rb ADDED
@@ -0,0 +1,109 @@
1
+ require 'rubygems'
2
+ require 'minitest/autorun'
3
+ require 'verm/client'
4
+ require 'byebug' rescue nil
5
+
6
+ class TestVerm < Minitest::Test
7
+ def setup
8
+ @client = Verm::Client.new(ENV["VERM_TEST_HOSTNAME"] || "localhost")
9
+ end
10
+
11
+ def fixture_file_path(filename)
12
+ File.join(File.dirname(__FILE__), 'fixtures', filename)
13
+ end
14
+
15
+ def test_stores_content_and_returns_location
16
+ assert_equal "/test/files/F0/ZdYVIlyqOiCKtR_oQF_9y8G8_9qAWhR9Fw5hzK8UM.txt",
17
+ @client.store("/test/files", "this is a test", "text/plain")
18
+
19
+ assert_equal "/test/files/F0/ZdYVIlyqOiCKtR_oQF_9y8G8_9qAWhR9Fw5hzK8UM.txt",
20
+ @client.store("/test/files/", "this is a test", "text/plain")
21
+
22
+ assert_equal "/test/files/F0/ZdYVIlyqOiCKtR_oQF_9y8G8_9qAWhR9Fw5hzK8UM",
23
+ @client.store("/test/files", "this is a test", "application/octet-stream")
24
+
25
+ assert_equal "/test/files/SL/VHO4KGrhBRAAjinYUEdqilkOrS4JD6akWSeJb5Tra.txt",
26
+ @client.store("/test/files", "this is a test\n", "text/plain")
27
+ end
28
+
29
+ def test_stores_files_and_returns_location
30
+ File.open fixture_file_path("binary_file") do |f|
31
+ assert_equal "/bins/IF/P8unS2JIuR6_UZI5pZ0lxWHhfvR2ocOcRAma_lEiA",
32
+ @client.store("/bins/", f, "application/octet-stream")
33
+
34
+ assert_equal "/bins/IF/P8unS2JIuR6_UZI5pZ0lxWHhfvR2ocOcRAma_lEiA.png",
35
+ @client.store("/bins/", f, "image/png")
36
+ end
37
+ end
38
+
39
+ def test_stores_compressed_files_and_returns_location_from_uncompressed_content
40
+ File.open fixture_file_path("binary_file.gz") do |f|
41
+ # note the hashes are the same here as the uncompressed file above
42
+ assert_equal "/bins/IF/P8unS2JIuR6_UZI5pZ0lxWHhfvR2ocOcRAma_lEiA",
43
+ @client.store("/bins/", f, "application/octet-stream", encoding: 'gzip')
44
+
45
+ assert_equal "/bins/IF/P8unS2JIuR6_UZI5pZ0lxWHhfvR2ocOcRAma_lEiA.png",
46
+ @client.store("/bins/", f, "image/png", encoding: 'gzip')
47
+ end
48
+
49
+ File.open fixture_file_path("large_compressed_csv_file.gz") do |f|
50
+ assert_equal "/csvs/VP/YY9KXv6cc6XksLTHOQJywCHnzyQDCd7Xh7HHUO2hd.csv",
51
+ @client.store("/csvs/", f, "text/csv", encoding: 'gzip')
52
+ end
53
+ end
54
+
55
+ def test_loads_content_and_type
56
+ content, type = "this is a test", "text/plain"
57
+ assert_equal [content, type],
58
+ @client.load(@client.store("/test/files_to_load", content, type))
59
+
60
+ content, type = File.binread(fixture_file_path("binary_file")), "application/octet-stream"
61
+ assert_equal [content, type],
62
+ @client.load(@client.store("/test/files_to_load", content, type))
63
+ end
64
+
65
+ def test_loads_and_uncompresses_compressed_content_and_type
66
+ compressed_content, type = File.binread(fixture_file_path("binary_file.gz")), "application/octet-stream"
67
+ uncompressed_content = File.binread(fixture_file_path("binary_file"))
68
+
69
+ assert_equal [uncompressed_content, type],
70
+ @client.load(@client.store("/test/compressed", compressed_content, type, encoding: 'gzip'))
71
+ end
72
+
73
+ def test_optionally_doesnt_uncompress_compressed_content
74
+ compressed_content, type = File.binread(fixture_file_path("binary_file.gz")), "application/octet-stream"
75
+
76
+ assert_equal [compressed_content, type],
77
+ @client.load(@client.store("/test/compressed", compressed_content, type, encoding: 'gzip'), 'Accept-Encoding' => 'gzip')
78
+ end
79
+
80
+ def test_marks_loaded_text_as_utf_8_by_default
81
+ compressed_content, type = File.binread(fixture_file_path("large_compressed_csv_file.gz")), "text/csv"
82
+ uncompressed_content = Zlib::GzipReader.new(StringIO.new(compressed_content)).read
83
+
84
+ assert_equal [uncompressed_content, type],
85
+ @client.load(@client.store("/test/compressed", compressed_content, type, encoding: 'gzip'))
86
+ end
87
+
88
+ def test_optionally_doesnt_mark_loaded_text_as_anything
89
+ compressed_content, type = File.binread(fixture_file_path("large_compressed_csv_file.gz")), "text/csv"
90
+ uncompressed_content = Zlib::GzipReader.new(StringIO.new(compressed_content)).read.force_encoding("ASCII-8BIT")
91
+
92
+ assert_equal [uncompressed_content, type],
93
+ @client.load(@client.store("/test/compressed", compressed_content, type, encoding: 'gzip'), force_text_encoding: nil)
94
+ end
95
+
96
+ def test_streams_content
97
+ compressed_content, type = File.binread(fixture_file_path("large_compressed_csv_file.gz")), "application/octet-stream"
98
+ uncompressed_content = Zlib::GzipReader.new(StringIO.new(compressed_content)).read.force_encoding("ASCII-8BIT")
99
+
100
+ result = ""
101
+ chunks = 0
102
+ @client.stream(@client.store("/test/compressed", uncompressed_content, type)) do |chunk|
103
+ result << chunk
104
+ chunks += 1
105
+ end
106
+ assert_equal uncompressed_content, result
107
+ assert chunks > 1, "should have been given the content in multiple chunks"
108
+ end
109
+ end
data/verm.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/verm/version', __FILE__)
3
+
4
+ spec = Gem::Specification.new do |gem|
5
+ gem.name = 'verm-client'
6
+ gem.version = Verm::VERSION
7
+ gem.license = "MIT"
8
+ gem.summary = "Tiny Ruby HTTP client for the Verm immutable, WORM filestore."
9
+ gem.description = <<-EOF
10
+ Adds one-line methods for storing files in Verm and retrieving them again.
11
+ EOF
12
+ gem.has_rdoc = false
13
+ gem.author = "Will Bryant"
14
+ gem.email = "will.bryant@gmail.com"
15
+ gem.homepage = "http://github.com/willbryant/verm-client-ruby"
16
+
17
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ gem.files = `git ls-files`.split("\n")
19
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ gem.require_path = "lib"
21
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: verm-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Will Bryant
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-11 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |
14
+ Adds one-line methods for storing files in Verm and retrieving them again.
15
+ email: will.bryant@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - MIT-LICENSE
22
+ - README.md
23
+ - Rakefile
24
+ - lib/verm.rb
25
+ - lib/verm/client.rb
26
+ - lib/verm/version.rb
27
+ - test/fixtures/binary_file
28
+ - test/fixtures/binary_file.gz
29
+ - test/fixtures/large_compressed_csv_file.gz
30
+ - test/verm_test.rb
31
+ - verm.gemspec
32
+ homepage: http://github.com/willbryant/verm-client-ruby
33
+ licenses:
34
+ - MIT
35
+ metadata: {}
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubyforge_project:
52
+ rubygems_version: 2.2.2
53
+ signing_key:
54
+ specification_version: 4
55
+ summary: Tiny Ruby HTTP client for the Verm immutable, WORM filestore.
56
+ test_files:
57
+ - test/fixtures/binary_file
58
+ - test/fixtures/binary_file.gz
59
+ - test/fixtures/large_compressed_csv_file.gz
60
+ - test/verm_test.rb