verm-client 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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