righttp 0.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.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/lib/rig/http.rb +178 -0
- data/lib/righttp.rb +5 -0
- data/righttp.gemspec +52 -0
- data/test/helper.rb +31 -0
- data/test/test_http.rb +96 -0
- metadata +76 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 hukl
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= righttp
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2010 hukl. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "righttp"
|
8
|
+
gem.summary = %Q{A HTTP library from scratch for ruby1.9}
|
9
|
+
gem.description = %Q{Why? Because it has to be done!}
|
10
|
+
gem.email = "contact@smyck.org"
|
11
|
+
gem.homepage = "http://github.com/hukl/righttp"
|
12
|
+
gem.authors = ["hukl"]
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
end
|
15
|
+
Jeweler::GemcutterTasks.new
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rake/testtask'
|
21
|
+
Rake::TestTask.new(:test) do |test|
|
22
|
+
test.libs << 'lib' << 'test'
|
23
|
+
test.pattern = 'test/**/test_*.rb'
|
24
|
+
test.verbose = true
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
require 'rcov/rcovtask'
|
29
|
+
Rcov::RcovTask.new do |test|
|
30
|
+
test.libs << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
rescue LoadError
|
35
|
+
task :rcov do
|
36
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
task :test => :check_dependencies
|
41
|
+
|
42
|
+
task :default => :test
|
43
|
+
|
44
|
+
require 'rake/rdoctask'
|
45
|
+
Rake::RDocTask.new do |rdoc|
|
46
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
47
|
+
|
48
|
+
rdoc.rdoc_dir = 'rdoc'
|
49
|
+
rdoc.title = "righttp #{version}"
|
50
|
+
rdoc.rdoc_files.include('README*')
|
51
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
52
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/lib/rig/http.rb
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Rig
|
4
|
+
|
5
|
+
CRLF = "\r\n"
|
6
|
+
|
7
|
+
class HTTP
|
8
|
+
|
9
|
+
attr_reader :tcp_socket, :params, :method, :path
|
10
|
+
|
11
|
+
def initialize options
|
12
|
+
@host = options[:host] || raise(ArgumentError, "No host specified")
|
13
|
+
@port = options[:port] || 80
|
14
|
+
@params = options[:params] || {}
|
15
|
+
@method = options[:method] || "GET"
|
16
|
+
@path = options[:path] || "/"
|
17
|
+
@header = HTTPHeader.new( "" => "#{@method} #{@path} HTTP/1.1" )
|
18
|
+
@custom_header = HTTPHeader.new( options[:header] || {} )
|
19
|
+
@body = HTTPBody.new
|
20
|
+
@tcp_socket = TCPSocket.new( @host, @port )
|
21
|
+
end
|
22
|
+
|
23
|
+
def generate_header_and_body
|
24
|
+
if @method == "GET"
|
25
|
+
update_path_query_params
|
26
|
+
else
|
27
|
+
update_body
|
28
|
+
end
|
29
|
+
update_header
|
30
|
+
end
|
31
|
+
|
32
|
+
def send
|
33
|
+
begin
|
34
|
+
generate_header_and_body
|
35
|
+
@tcp_socket.write( header + body )
|
36
|
+
response = @tcp_socket.recvfrom(2**16)
|
37
|
+
rescue => exception
|
38
|
+
puts exception.message
|
39
|
+
ensure
|
40
|
+
@tcp_socket.close
|
41
|
+
end
|
42
|
+
|
43
|
+
response || exception.message
|
44
|
+
end
|
45
|
+
|
46
|
+
def update_header
|
47
|
+
@header.merge!(
|
48
|
+
"Host" => "localhost",
|
49
|
+
"Origin" => "localhost",
|
50
|
+
"Content-Length" => @body.join.bytes.to_a.length,
|
51
|
+
"Content-Type" => determine_content_type
|
52
|
+
).merge!(
|
53
|
+
@custom_header
|
54
|
+
).merge!(
|
55
|
+
"Connection" => "close"
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def update_path_query_params
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def multipart?
|
64
|
+
if defined? @multipart
|
65
|
+
@multipart
|
66
|
+
else
|
67
|
+
@multipart = @params.values.map(&:class).include?( File )
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def update_body
|
72
|
+
if multipart?
|
73
|
+
create_multipart_body
|
74
|
+
else
|
75
|
+
create_simple_body
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def new_text_multipart field_name, text
|
80
|
+
part = ""
|
81
|
+
part += "--#{boundary}"
|
82
|
+
part += CRLF
|
83
|
+
part += "Content-Disposition: form-data; name=\"#{field_name}\""
|
84
|
+
part += CRLF
|
85
|
+
part += CRLF
|
86
|
+
part += text
|
87
|
+
part += CRLF
|
88
|
+
end
|
89
|
+
|
90
|
+
def new_file_multipart field_name, file
|
91
|
+
content_type = %x[file --mime-type -b #{file.path}].chomp
|
92
|
+
|
93
|
+
part = ""
|
94
|
+
part += "--#{boundary}"
|
95
|
+
part += CRLF
|
96
|
+
part += "Content-Disposition: form-data; name=\"#{field_name}\"; "
|
97
|
+
part += "filename=\"#{File.basename( file )}\""
|
98
|
+
part += CRLF
|
99
|
+
part += "Content-Type: #{content_type}"
|
100
|
+
part += CRLF
|
101
|
+
part += CRLF
|
102
|
+
part += file.read
|
103
|
+
file.close
|
104
|
+
part += CRLF
|
105
|
+
end
|
106
|
+
|
107
|
+
def create_multipart_body
|
108
|
+
@params.each do |key, value|
|
109
|
+
if value.is_a?( File )
|
110
|
+
@body << new_file_multipart( key, value )
|
111
|
+
elsif value.is_a?( String ) || value.responds_to?( :to_s )
|
112
|
+
@body << new_text_multipart( key, value )
|
113
|
+
else
|
114
|
+
raise ArgumentError, "Invalid Parameter Value"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
@body << "--#{boundary}--\r\n"
|
119
|
+
end
|
120
|
+
|
121
|
+
def create_simple_body
|
122
|
+
@body << @params.map {|key, value| "#{key}=#{value}"}.join("&")
|
123
|
+
end
|
124
|
+
|
125
|
+
def determine_content_type
|
126
|
+
if multipart?
|
127
|
+
"multipart/form-data; boundary=#{boundary}"
|
128
|
+
else
|
129
|
+
if @method == "POST"
|
130
|
+
"application/x-www-form-urlencoded; charset=UTF-8"
|
131
|
+
else
|
132
|
+
"text/plain"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def boundary
|
138
|
+
@boundary ||= "----rigHTTPmultipart#{rand(2**32)}XZWCFOOBAR"
|
139
|
+
end
|
140
|
+
|
141
|
+
def header
|
142
|
+
@header.to_s
|
143
|
+
end
|
144
|
+
|
145
|
+
def body
|
146
|
+
@body.to_s
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class HTTPHeader < Hash
|
151
|
+
|
152
|
+
def initialize options
|
153
|
+
super.merge! options
|
154
|
+
end
|
155
|
+
|
156
|
+
def to_s
|
157
|
+
header_string = map do |field_name, value|
|
158
|
+
if field_name == ""
|
159
|
+
value
|
160
|
+
else
|
161
|
+
"#{field_name}: #{value}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
header_string.join(CRLF) + CRLF + CRLF
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
class HTTPBody < Array
|
171
|
+
|
172
|
+
def to_s
|
173
|
+
join
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
data/righttp.gemspec
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{righttp}
|
8
|
+
s.version = "0.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["hukl"]
|
12
|
+
s.date = %q{2010-07-06}
|
13
|
+
s.description = %q{Why? Because it has to be done!}
|
14
|
+
s.email = %q{contact@smyck.org}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/rig/http.rb",
|
27
|
+
"lib/righttp.rb",
|
28
|
+
"righttp.gemspec",
|
29
|
+
"test/helper.rb",
|
30
|
+
"test/test_http.rb"
|
31
|
+
]
|
32
|
+
s.homepage = %q{http://github.com/hukl/righttp}
|
33
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
s.rubygems_version = %q{1.3.7}
|
36
|
+
s.summary = %q{A HTTP library from scratch for ruby1.9}
|
37
|
+
s.test_files = [
|
38
|
+
"test/helper.rb",
|
39
|
+
"test/test_http.rb"
|
40
|
+
]
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
44
|
+
s.specification_version = 3
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
47
|
+
else
|
48
|
+
end
|
49
|
+
else
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
data/test/helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
5
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib', 'rig'))
|
7
|
+
|
8
|
+
require 'http'
|
9
|
+
|
10
|
+
class Test::Unit::TestCase
|
11
|
+
include Rig
|
12
|
+
|
13
|
+
unless defined?(Spec)
|
14
|
+
# test "verify something" do
|
15
|
+
# ...
|
16
|
+
# end
|
17
|
+
def self.test(name, &block)
|
18
|
+
test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
|
19
|
+
defined = instance_method(test_name) rescue false
|
20
|
+
raise "#{test_name} is already defined in #{self}" if defined
|
21
|
+
if block_given?
|
22
|
+
define_method(test_name, &block)
|
23
|
+
else
|
24
|
+
define_method(test_name) do
|
25
|
+
flunk "No implementation provided for #{name}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
data/test/test_http.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestHttp < Test::Unit::TestCase
|
4
|
+
|
5
|
+
test "create the simplest http get object" do
|
6
|
+
assert_not_nil get = HTTP.new( {:host => "localhost"} )
|
7
|
+
end
|
8
|
+
|
9
|
+
test "to create a http object at least the host must be specified" do
|
10
|
+
assert_raise(ArgumentError) { get = HTTP.new( {} ) }
|
11
|
+
end
|
12
|
+
|
13
|
+
test "http objects have accessible socket object" do
|
14
|
+
get = HTTP.new( {:host => "localhost"} )
|
15
|
+
assert_not_nil get.tcp_socket
|
16
|
+
end
|
17
|
+
|
18
|
+
test "http object has accessible params" do
|
19
|
+
get = HTTP.new( {:host => "localhost", :params => {"foo" => "bar"}} )
|
20
|
+
assert_not_nil get.params
|
21
|
+
assert_equal ({"foo" => "bar"}), get.params
|
22
|
+
end
|
23
|
+
|
24
|
+
test "http object without params specified returns empty params" do
|
25
|
+
get = HTTP.new( {:host => "localhost"} )
|
26
|
+
assert_not_nil get.params
|
27
|
+
assert_equal ({}), get.params
|
28
|
+
end
|
29
|
+
|
30
|
+
test "simple http object defaults to method GET" do
|
31
|
+
get = HTTP.new( {:host => "localhost"} )
|
32
|
+
assert_equal "GET", get.method
|
33
|
+
end
|
34
|
+
|
35
|
+
test "method of http object can be overridden" do
|
36
|
+
post = HTTP.new( {:host => "localhost", :method => "POST"} )
|
37
|
+
assert_equal "POST", post.method
|
38
|
+
end
|
39
|
+
|
40
|
+
test "path of a http object defaults to index" do
|
41
|
+
get = HTTP.new( {:host => "localhost"} )
|
42
|
+
assert_equal "/", get.path
|
43
|
+
end
|
44
|
+
|
45
|
+
test "path of http object can be set" do
|
46
|
+
get = HTTP.new( {:host => "localhost", :path => "/posts"} )
|
47
|
+
assert_equal "/posts", get.path
|
48
|
+
end
|
49
|
+
|
50
|
+
test "generate_header_and_body" do
|
51
|
+
get = HTTP.new( {:host => "localhost", :path => "/posts"} )
|
52
|
+
assert_not_nil get.generate_header_and_body
|
53
|
+
|
54
|
+
expected = "GET /posts HTTP/1.1\r\n" \
|
55
|
+
"Host: localhost\r\n" \
|
56
|
+
"Origin: localhost\r\n" \
|
57
|
+
"Content-Length: 0\r\n" \
|
58
|
+
"Content-Type: text/plain\r\n" \
|
59
|
+
"Connection: close\r\n\r\n"
|
60
|
+
|
61
|
+
assert_equal expected, get.header
|
62
|
+
end
|
63
|
+
|
64
|
+
test "multipart body gets properly created" do
|
65
|
+
post = HTTP.new(
|
66
|
+
:host => "localhost",
|
67
|
+
:path => "/photos",
|
68
|
+
:method => "POST",
|
69
|
+
:params => {
|
70
|
+
"photo[title]" => "Hello World",
|
71
|
+
"photo[image]" => File.open("/Users/hukl/Desktop/file1.png"),
|
72
|
+
"photo[picture]" => File.open("/Users/hukl/Desktop/file2.png")
|
73
|
+
}
|
74
|
+
)
|
75
|
+
post.generate_header_and_body
|
76
|
+
|
77
|
+
post.tcp_socket.write (post.header + post.body)
|
78
|
+
response = post.tcp_socket.recvfrom(2**16)
|
79
|
+
status = response.first.match(/HTTP\/1\.1\s(\d\d\d).+$/)[1]
|
80
|
+
|
81
|
+
assert_equal 302, status.to_i
|
82
|
+
end
|
83
|
+
|
84
|
+
test "real get request" do
|
85
|
+
get = HTTP.new(
|
86
|
+
:host => "localhost",
|
87
|
+
:path => "/photos"
|
88
|
+
)
|
89
|
+
|
90
|
+
get.tcp_socket.write (get.header + get.body)
|
91
|
+
response = get.tcp_socket.recvfrom(2**16)
|
92
|
+
status = response.first.match(/HTTP\/1\.1\s(\d\d\d).+$/)[1]
|
93
|
+
|
94
|
+
assert_equal 200, status.to_i
|
95
|
+
end
|
96
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: righttp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
version: 0.0.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- hukl
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-07-06 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Why? Because it has to be done!
|
22
|
+
email: contact@smyck.org
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- LICENSE
|
29
|
+
- README.rdoc
|
30
|
+
files:
|
31
|
+
- .document
|
32
|
+
- .gitignore
|
33
|
+
- LICENSE
|
34
|
+
- README.rdoc
|
35
|
+
- Rakefile
|
36
|
+
- VERSION
|
37
|
+
- lib/rig/http.rb
|
38
|
+
- lib/righttp.rb
|
39
|
+
- righttp.gemspec
|
40
|
+
- test/helper.rb
|
41
|
+
- test/test_http.rb
|
42
|
+
has_rdoc: true
|
43
|
+
homepage: http://github.com/hukl/righttp
|
44
|
+
licenses: []
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options:
|
48
|
+
- --charset=UTF-8
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.3.7
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: A HTTP library from scratch for ruby1.9
|
74
|
+
test_files:
|
75
|
+
- test/helper.rb
|
76
|
+
- test/test_http.rb
|