distribustream 0.1.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/CHANGES +2 -0
- data/COPYING +674 -0
- data/README +107 -0
- data/Rakefile +44 -0
- data/bin/distribustream +60 -0
- data/bin/dsclient +43 -0
- data/bin/dsseed +103 -0
- data/conf/bigchunk.yml +18 -0
- data/conf/debug.yml +18 -0
- data/conf/example.yml +18 -0
- data/distribustream.gemspec +20 -0
- data/lib/pdtp/client.rb +195 -0
- data/lib/pdtp/client/file_buffer.rb +128 -0
- data/lib/pdtp/client/file_buffer_spec.rb +154 -0
- data/lib/pdtp/client/file_service.rb +60 -0
- data/lib/pdtp/client/protocol.rb +66 -0
- data/lib/pdtp/client/transfer.rb +229 -0
- data/lib/pdtp/common/common_init.rb +122 -0
- data/lib/pdtp/common/file_service.rb +69 -0
- data/lib/pdtp/common/file_service_spec.rb +91 -0
- data/lib/pdtp/common/protocol.rb +346 -0
- data/lib/pdtp/common/protocol_spec.rb +68 -0
- data/lib/pdtp/server.rb +368 -0
- data/lib/pdtp/server/client_info.rb +140 -0
- data/lib/pdtp/server/file_service.rb +89 -0
- data/lib/pdtp/server/transfer.rb +62 -0
- data/lib/pdtp/server/trust.rb +88 -0
- data/lib/pdtp/server/trust_spec.rb +40 -0
- metadata +114 -0
@@ -0,0 +1,128 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
|
3
|
+
# All rights reserved. See COPYING for permissions.
|
4
|
+
#
|
5
|
+
# This source file is distributed as part of the
|
6
|
+
# DistribuStream file transfer system.
|
7
|
+
#
|
8
|
+
# See http://distribustream.rubyforge.org/
|
9
|
+
#++
|
10
|
+
|
11
|
+
module PDTP
|
12
|
+
# Handle a file buffer, which may be written to and read from randomly
|
13
|
+
class FileBuffer
|
14
|
+
def initialize(io = nil)
|
15
|
+
@io = io
|
16
|
+
@written = 0
|
17
|
+
@entries = []
|
18
|
+
end
|
19
|
+
|
20
|
+
# Write data starting at start_pos. Overwrites any existing data in that block
|
21
|
+
def write(start_pos, data)
|
22
|
+
return if data.size == 0
|
23
|
+
|
24
|
+
# create and entry and attempt to combine it with old entries
|
25
|
+
new_entry = Entry.new(start_pos,data)
|
26
|
+
|
27
|
+
intersections = true
|
28
|
+
while intersections
|
29
|
+
intersections = false
|
30
|
+
@entries.each do |e|
|
31
|
+
if intersects?(new_entry, e)
|
32
|
+
new_entry = combine(e, new_entry)
|
33
|
+
@entries.delete(e)
|
34
|
+
intersections = true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Add entry to the local store
|
40
|
+
@entries << new_entry
|
41
|
+
|
42
|
+
# Write contiguous blocks we receive to our internal IO cursor
|
43
|
+
if @io and start_pos == @written
|
44
|
+
data_begin = @written - new_entry.start_pos
|
45
|
+
bytes_written = @io.write(new_entry.data[data_begin..new_entry.data.length])
|
46
|
+
@written += bytes_written
|
47
|
+
else
|
48
|
+
bytes_written = data.size
|
49
|
+
end
|
50
|
+
|
51
|
+
bytes_written
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns a string containing the desired data.
|
55
|
+
# Returns nil if the data is not all there
|
56
|
+
def read(range)
|
57
|
+
return nil if range.first>range.last
|
58
|
+
current_byte=range.first
|
59
|
+
|
60
|
+
buffer = ''
|
61
|
+
|
62
|
+
while current_byte <= range.last do
|
63
|
+
# find an entry that contains this byte
|
64
|
+
|
65
|
+
found=false
|
66
|
+
@entries.each do |e|
|
67
|
+
if e.range.include?(current_byte)
|
68
|
+
internal_start=current_byte-e.start_pos #start position inside this entry's data
|
69
|
+
internal_end=(range.last<e.end_pos ? range.last : e.end_pos) - e.start_pos
|
70
|
+
buffer << e.data[internal_start..internal_end]
|
71
|
+
current_byte+=internal_end-internal_start+1
|
72
|
+
found=true
|
73
|
+
break if current_byte>range.last
|
74
|
+
end
|
75
|
+
end
|
76
|
+
return nil if found==false
|
77
|
+
end
|
78
|
+
|
79
|
+
buffer
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns true if two entries intersect
|
83
|
+
def intersects?(entry1, entry2)
|
84
|
+
first, last = entry1.start_pos <= entry2.start_pos ? [entry1, entry2] : [entry2, entry1]
|
85
|
+
first.end_pos + 1 >= last.start_pos
|
86
|
+
end
|
87
|
+
|
88
|
+
# Takes two Entries
|
89
|
+
# Returns nil if there is no intersection
|
90
|
+
# Returns the union if they intersect
|
91
|
+
def combine(old_entry, new_entry)
|
92
|
+
start = old_entry.start_pos < new_entry.start_pos ? old_entry.start_pos: new_entry.start_pos
|
93
|
+
|
94
|
+
stringio = StringIO.new
|
95
|
+
stringio.seek(old_entry.start_pos - start)
|
96
|
+
stringio.write(old_entry.data)
|
97
|
+
stringio.seek(new_entry.start_pos - start)
|
98
|
+
stringio.write(new_entry.data)
|
99
|
+
return Entry.new(start, stringio.string)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Return number of bytes currently in the buffer
|
103
|
+
def bytes_stored
|
104
|
+
bytes=0
|
105
|
+
@entries.each do |e|
|
106
|
+
bytes=bytes+e.data.size
|
107
|
+
end
|
108
|
+
return bytes
|
109
|
+
end
|
110
|
+
|
111
|
+
# Container for an entry in the buffer
|
112
|
+
class Entry
|
113
|
+
def initialize(start_pos,data)
|
114
|
+
@start_pos,@data=start_pos,data
|
115
|
+
end
|
116
|
+
|
117
|
+
attr_accessor :start_pos, :data
|
118
|
+
|
119
|
+
def end_pos
|
120
|
+
@start_pos + data.length - 1
|
121
|
+
end
|
122
|
+
|
123
|
+
def range
|
124
|
+
Range.new(@start_pos,end_pos)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
|
3
|
+
# All rights reserved. See COPYING for permissions.
|
4
|
+
#
|
5
|
+
# This source file is distributed as part of the
|
6
|
+
# DistribuStream file transfer system.
|
7
|
+
#
|
8
|
+
# See http://distribustream.rubyforge.org/
|
9
|
+
#++
|
10
|
+
|
11
|
+
require File.dirname(__FILE__) + '/file_buffer'
|
12
|
+
|
13
|
+
describe PDTP::FileBuffer do
|
14
|
+
before(:each) do
|
15
|
+
@b = PDTP::FileBuffer.new
|
16
|
+
end
|
17
|
+
|
18
|
+
it "returns nil if read when empty" do
|
19
|
+
@b.bytes_stored.should == 0
|
20
|
+
@b.read(0..1).should == nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe PDTP::FileBuffer, "with one entry" do
|
25
|
+
before(:each) do
|
26
|
+
@b = PDTP::FileBuffer.new
|
27
|
+
@b.write 0, 'hello'
|
28
|
+
end
|
29
|
+
|
30
|
+
it "calculates bytes stored correctly" do
|
31
|
+
@b.bytes_stored.should == 5
|
32
|
+
end
|
33
|
+
|
34
|
+
it "reads stored data correctly" do
|
35
|
+
@b.read(0..4).should == "hello"
|
36
|
+
@b.read(1..1).should == "e"
|
37
|
+
@b.read(-1..2).should == nil
|
38
|
+
@b.read(0..5).should == nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe PDTP::FileBuffer, "with two overlapping entries" do
|
43
|
+
before(:each) do
|
44
|
+
@b = PDTP::FileBuffer.new
|
45
|
+
@b.write(3,"hello")
|
46
|
+
@b.write(7,"World")
|
47
|
+
end
|
48
|
+
|
49
|
+
it "calculates bytes stored correctly" do
|
50
|
+
@b.bytes_stored.should == 9
|
51
|
+
end
|
52
|
+
|
53
|
+
it "reads stored data correctly" do
|
54
|
+
@b.read(3..12).should == nil
|
55
|
+
@b.read(3..11).should == "hellWorld"
|
56
|
+
@b.read(3..1).should == nil
|
57
|
+
@b.read(2..4).should == nil
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
describe PDTP::FileBuffer, "with three overlapping entries" do
|
63
|
+
before(:each) do
|
64
|
+
@b = PDTP::FileBuffer.new
|
65
|
+
@b.write(3,"hello")
|
66
|
+
@b.write(7,"World")
|
67
|
+
@b.write(2,"123456789ABCDEF")
|
68
|
+
end
|
69
|
+
|
70
|
+
it "calculates bytes stored correctly" do
|
71
|
+
@b.bytes_stored.should == 15
|
72
|
+
end
|
73
|
+
|
74
|
+
it "reads stored data correctly" do
|
75
|
+
@b.read(2..16).should == "123456789ABCDEF"
|
76
|
+
@b.read(2..17).should == nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe PDTP::FileBuffer, "with two tangential entries" do
|
81
|
+
before(:each) do
|
82
|
+
@b = PDTP::FileBuffer.new
|
83
|
+
@b.write(3,"hello")
|
84
|
+
@b.write(8,"World")
|
85
|
+
end
|
86
|
+
|
87
|
+
it "calculates bytes stored correctly" do
|
88
|
+
@b.bytes_stored.should == 10
|
89
|
+
end
|
90
|
+
|
91
|
+
it "reads stored data correctly" do
|
92
|
+
@b.read(3..12).should == "helloWorld"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe PDTP::FileBuffer, "with a chain of overlapping entries" do
|
97
|
+
before(:each) do
|
98
|
+
@b = PDTP::FileBuffer.new
|
99
|
+
@b.write(3,"a123")
|
100
|
+
@b.write(4,"b4")
|
101
|
+
@b.write(0,"012c")
|
102
|
+
|
103
|
+
#___a123
|
104
|
+
#___ab43
|
105
|
+
#012cb43
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
it "calculates bytes stored correctly" do
|
110
|
+
@b.bytes_stored.should == 7
|
111
|
+
end
|
112
|
+
|
113
|
+
it "reads stored data correctly" do
|
114
|
+
@b.read(0..6).should == "012cb43"
|
115
|
+
@b.read(3..6).should == "cb43"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe PDTP::FileBuffer, "with an associated IO object" do
|
120
|
+
before(:each) do
|
121
|
+
@io = mock(:io)
|
122
|
+
@b = PDTP::FileBuffer.new @io
|
123
|
+
end
|
124
|
+
|
125
|
+
it "writes received data to the IO object" do
|
126
|
+
@io.should_receive(:write).once.with('foo').and_return(3)
|
127
|
+
@b.write(0, "foo")
|
128
|
+
end
|
129
|
+
|
130
|
+
it "writes successively received data to the IO object" do
|
131
|
+
@io.should_receive(:write).once.with('foo').and_return(3)
|
132
|
+
@b.write(0, "foo")
|
133
|
+
|
134
|
+
@io.should_receive(:write).once.with('bar').and_return(3)
|
135
|
+
@b.write(3, "bar")
|
136
|
+
|
137
|
+
@io.should_receive(:write).once.with('baz').and_return(3)
|
138
|
+
@b.write(6, "baz")
|
139
|
+
end
|
140
|
+
|
141
|
+
it "reassembles single-byte out-of-order data and writes it to the IO object" do
|
142
|
+
@io.should_receive(:write).once.with('bar').and_return(3)
|
143
|
+
@b.write(1, 'a')
|
144
|
+
@b.write(2, 'r')
|
145
|
+
@b.write(0, 'b')
|
146
|
+
end
|
147
|
+
|
148
|
+
it "reassembles multibyte out-of-order data and writes it to the IO object" do
|
149
|
+
@io.should_receive(:write).once.with('foobar').and_return(6)
|
150
|
+
@b.write(2, 'ob')
|
151
|
+
@b.write(4, 'ar')
|
152
|
+
@b.write(0, 'fo')
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
|
3
|
+
# All rights reserved. See COPYING for permissions.
|
4
|
+
#
|
5
|
+
# This source file is distributed as part of the
|
6
|
+
# DistribuStream file transfer system.
|
7
|
+
#
|
8
|
+
# See http://distribustream.rubyforge.org/
|
9
|
+
#++
|
10
|
+
|
11
|
+
require 'uri'
|
12
|
+
require 'pathname'
|
13
|
+
require File.dirname(__FILE__) + '/../common/file_service.rb'
|
14
|
+
require File.dirname(__FILE__) + '/file_buffer.rb'
|
15
|
+
|
16
|
+
module PDTP
|
17
|
+
class Client < Mongrel::HttpHandler
|
18
|
+
# The client specific file utilities. Most importantly, handling
|
19
|
+
# the data buffer.
|
20
|
+
class FileInfo < PDTP::FileInfo
|
21
|
+
def initialize(filename)
|
22
|
+
@buffer = FileBuffer.new open(filename, 'w')
|
23
|
+
@lock = Mutex.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# Write data into buffer starting at start_pos
|
27
|
+
def write(start_pos,data)
|
28
|
+
@lock.synchronize { @buffer.write start_pos, data }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Read a range of data out of buffer. Takes a ruby Range object
|
32
|
+
def read(range)
|
33
|
+
begin
|
34
|
+
@lock.synchronize { @buffer.read range }
|
35
|
+
rescue nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return the number of bytes currently stored
|
40
|
+
def bytes_downloaded
|
41
|
+
@lock.synchronize { @buffer.bytes_stored }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Container class for file data
|
46
|
+
class FileService < PDTP::FileService
|
47
|
+
def initialize
|
48
|
+
@files = {}
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_info(url)
|
52
|
+
@files[url] rescue nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def set_info(url, info)
|
56
|
+
@files[url] = info
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
|
3
|
+
# All rights reserved. See COPYING for permissions.
|
4
|
+
#
|
5
|
+
# This source file is distributed as part of the
|
6
|
+
# DistribuStream file transfer system.
|
7
|
+
#
|
8
|
+
# See http://distribustream.rubyforge.org/
|
9
|
+
#++
|
10
|
+
|
11
|
+
module PDTP
|
12
|
+
class Client < Mongrel::HttpHandler
|
13
|
+
# Implementation of a ruby test client for the pdtp protocol
|
14
|
+
class Protocol < PDTP::Protocol
|
15
|
+
def initialize *args
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
# Called after a connection to the server has been established
|
20
|
+
def connection_completed
|
21
|
+
begin
|
22
|
+
listen_port = @@config[:listen_port]
|
23
|
+
|
24
|
+
#create the client
|
25
|
+
client = PDTP::Client.new
|
26
|
+
PDTP::Protocol.listener = client
|
27
|
+
client.server_connection = self
|
28
|
+
client.generate_client_id listen_port
|
29
|
+
client.file_service = PDTP::Client::FileService.new
|
30
|
+
|
31
|
+
# Start a mongrel server on the specified port. If it isnt available, keep trying higher ports
|
32
|
+
begin
|
33
|
+
mongrel_server = Mongrel::HttpServer.new('0.0.0.0', listen_port)
|
34
|
+
rescue Exception => e
|
35
|
+
listen_port += 1
|
36
|
+
retry
|
37
|
+
end
|
38
|
+
|
39
|
+
@@log.info "listening on port #{listen_port}"
|
40
|
+
mongrel_server.register '/', client
|
41
|
+
mongrel_server.run
|
42
|
+
|
43
|
+
# Tell the server about ourself
|
44
|
+
send_message :client_info, :listen_port => listen_port, :client_id => client.my_id
|
45
|
+
|
46
|
+
# Ask the server for some information on the file we want
|
47
|
+
send_message :ask_info, :url => @@config[:request_url]
|
48
|
+
|
49
|
+
# Request the file
|
50
|
+
send_message :request, :url => @@config[:request_url]
|
51
|
+
|
52
|
+
@@log.info "This client is requesting"
|
53
|
+
rescue Exception=>e
|
54
|
+
puts "Exception in connection_completed: #{e}"
|
55
|
+
puts e.backtrace.join("\n")
|
56
|
+
exit
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def unbind
|
61
|
+
super
|
62
|
+
puts 'Disconnected from PDTP server.'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
|
3
|
+
# All rights reserved. See COPYING for permissions.
|
4
|
+
#
|
5
|
+
# This source file is distributed as part of the
|
6
|
+
# DistribuStream file transfer system.
|
7
|
+
#
|
8
|
+
# See http://distribustream.rubyforge.org/
|
9
|
+
#++
|
10
|
+
|
11
|
+
require File.dirname(__FILE__) + '/file_service'
|
12
|
+
require "thread"
|
13
|
+
require "net/http"
|
14
|
+
require "uri"
|
15
|
+
require "digest/sha2"
|
16
|
+
|
17
|
+
module PDTP
|
18
|
+
class Client < Mongrel::HttpHandler
|
19
|
+
module Transfer
|
20
|
+
# Generic HTTP Exception to be used on error
|
21
|
+
class HTTPException < Exception
|
22
|
+
attr_accessor :code
|
23
|
+
def initialize(code,message)
|
24
|
+
super(message)
|
25
|
+
@code = code
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# The base information and methods needed by client transfers
|
30
|
+
class Base
|
31
|
+
attr_reader :peer, :peer_id, :url, :byte_range
|
32
|
+
attr_reader :server_connection, :file_service
|
33
|
+
attr_reader :method, :client, :hash
|
34
|
+
|
35
|
+
# Returns true if a server message matches this transfer
|
36
|
+
def matches_message?(message)
|
37
|
+
@peer == message["peer"] and
|
38
|
+
@url == message["url"] and
|
39
|
+
@byte_range == message["range"] and
|
40
|
+
@peer_id == message["peer_id"]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Takes an HTTP range and returns a ruby Range object
|
44
|
+
def parse_http_range(string)
|
45
|
+
begin
|
46
|
+
raise "Can't parse range string: #{string}" unless string =~ /bytes=([0-9]+)-([0-9]+)/
|
47
|
+
(($1).to_i)..(($2).to_i)
|
48
|
+
rescue nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Notify the server of transfer completion.
|
53
|
+
# Hash field is used to denote success or failure
|
54
|
+
def send_completed_message(hash)
|
55
|
+
@server_connection.send_message(:completed,
|
56
|
+
:url => @url,
|
57
|
+
:peer => @peer,
|
58
|
+
:range => @byte_range,
|
59
|
+
:peer_id => @peer_id,
|
60
|
+
:hash => hash
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def send_ask_verify_message
|
65
|
+
@server_connection.send_message(:ask_verify,
|
66
|
+
:url => @url,
|
67
|
+
:peer => @peer,
|
68
|
+
:range => @byte_range,
|
69
|
+
:peer_id => @peer_id
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Implements the listening end (the server) of a peer to peer http connection
|
75
|
+
class Listener < Base
|
76
|
+
attr :request, :response
|
77
|
+
|
78
|
+
# Called with the request and response parameters given by Mongrel
|
79
|
+
def initialize(request,response,server_connection,file_service,client)
|
80
|
+
@request,@response=request,response
|
81
|
+
@server_connection,@file_service=server_connection,file_service
|
82
|
+
@authorized=false
|
83
|
+
@client=client
|
84
|
+
end
|
85
|
+
|
86
|
+
# Send an HTTP error response to requester
|
87
|
+
def write_http_exception(e)
|
88
|
+
if e.class == HTTPException
|
89
|
+
@response.start(e.code) do |head,out|
|
90
|
+
out.write(e.to_s + "\n\n" + e.backtrace.join("\n") )
|
91
|
+
end
|
92
|
+
else
|
93
|
+
@@log.info("MONGREL SERVER ERROR: exception:" + e.to_s+"\n\n"+e.backtrace.join("\n"))
|
94
|
+
@response.start(500) do |head,out|
|
95
|
+
out.write("Server error, unknown exception:"+e.to_s + "\n\n" + e.backtrace.join("\n") )
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Parse the HTTP header and ask for verification of transfer
|
101
|
+
# Thread is stopped after asking for verification and will
|
102
|
+
# be restarted when verification arrives
|
103
|
+
def handle_header
|
104
|
+
@thread=Thread.current
|
105
|
+
|
106
|
+
@@log.debug "params=#{@request.params.inspect}"
|
107
|
+
|
108
|
+
@method=@request.params["REQUEST_METHOD"].downcase
|
109
|
+
@peer=@request.params["REMOTE_ADDR"]
|
110
|
+
|
111
|
+
#here we construct the GUID for this file
|
112
|
+
path=@request.params["REQUEST_PATH"]
|
113
|
+
vhost=@request.params["HTTP_HOST"]
|
114
|
+
@url="http://"+vhost+path
|
115
|
+
|
116
|
+
@byte_range=parse_http_range(request.params["HTTP_RANGE"])
|
117
|
+
@peer_id=@request.params["HTTP_X_PDTP_PEER_ID"]
|
118
|
+
|
119
|
+
#sanity checking
|
120
|
+
raise HTTPException.new(400, "Missing X-PDTP-Peer-Id header") if @peer_id.nil?
|
121
|
+
raise HTTPException.new(400, "Missing Host header") if vhost.nil?
|
122
|
+
raise HTTPException.new(400, "Missing Range header") if @byte_range.nil?
|
123
|
+
|
124
|
+
send_ask_verify_message
|
125
|
+
Thread.stop
|
126
|
+
after_verification
|
127
|
+
end
|
128
|
+
|
129
|
+
# Called after receiving verification message from the server
|
130
|
+
# Set the authorized status and restart the thread
|
131
|
+
# This throws us into after_verification
|
132
|
+
def tell_verify(authorized)
|
133
|
+
@authorized=authorized
|
134
|
+
@thread.run
|
135
|
+
end
|
136
|
+
|
137
|
+
# Perform the transfer if verification was successful
|
138
|
+
def after_verification
|
139
|
+
#check if the server authorized us
|
140
|
+
unless @authorized
|
141
|
+
raise HTTPException.new(403,"Forbidden: the server did not authorize this transfer")
|
142
|
+
end
|
143
|
+
|
144
|
+
info = @file_service.get_info(@url)
|
145
|
+
if @method == "put"
|
146
|
+
#we are the taker
|
147
|
+
@@log.debug("Body Downloaded: url=#{@url} range=#{@byte_range} peer=#{@peer}")
|
148
|
+
|
149
|
+
@file_service.set_info(FileInfo.new) if info.nil?
|
150
|
+
info.write(@byte_range.first, @request.body.read)
|
151
|
+
@hash=Digest::SHA256.hexdigest(res.body) rescue nil
|
152
|
+
|
153
|
+
# Stock HTTP OK response
|
154
|
+
@response.start(200) do |head,out|
|
155
|
+
end
|
156
|
+
elsif @method=="get"
|
157
|
+
#we are the giver
|
158
|
+
raise HTTPException.new(404,"File not found: #{@url}") if info.nil?
|
159
|
+
data=info.read(@byte_range)
|
160
|
+
raise HTTPException.new(416,"Invalid range: #{@byte_range.inspect}") if data.nil?
|
161
|
+
|
162
|
+
#Request was GET, so now we need to send the data
|
163
|
+
@response.start(206) do |head, out|
|
164
|
+
head['Content-Type'] = 'application/octet-stream'
|
165
|
+
head['Content-Range'] = "bytes #{@byte_range.first}-#{@byte_range.last}/*"
|
166
|
+
#FIXME must include a DATE header according to http
|
167
|
+
|
168
|
+
out.write(data)
|
169
|
+
end
|
170
|
+
else
|
171
|
+
raise HTTPException.new(405,"Invalid method: #{@method}")
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
# Implements http transfer between two peers from the connector's (client) perspective
|
179
|
+
class Connector < Base
|
180
|
+
def initialize(message,server_connection,file_service,client)
|
181
|
+
@server_connection,@file_service=server_connection,file_service
|
182
|
+
@peer,@port=message["host"],message["port"]
|
183
|
+
@method = message["method"]
|
184
|
+
@url=message["url"]
|
185
|
+
@byte_range=message["range"]
|
186
|
+
@peer_id=message["peer_id"]
|
187
|
+
@client=client
|
188
|
+
end
|
189
|
+
|
190
|
+
# Perform the transfer
|
191
|
+
def run
|
192
|
+
hash=nil
|
193
|
+
|
194
|
+
info=@file_service.get_info(@url)
|
195
|
+
|
196
|
+
#compute the vhost and path
|
197
|
+
#FIXME work with ports
|
198
|
+
uri=URI.split(@url)
|
199
|
+
path=uri[5]
|
200
|
+
vhost=uri[2]
|
201
|
+
|
202
|
+
if @method == "get"
|
203
|
+
req = Net::HTTP::Get.new(path)
|
204
|
+
body = nil
|
205
|
+
elsif @method == "put"
|
206
|
+
req = Net::HTTP::Put.new(path)
|
207
|
+
body = info.read(@byte_range)
|
208
|
+
else
|
209
|
+
raise HTTPException.new(405,"Invalid method: #{@method}")
|
210
|
+
end
|
211
|
+
|
212
|
+
req.add_field("Range", "bytes=#{@byte_range.begin}-#{@byte_range.end}")
|
213
|
+
req.add_field("Host",vhost)
|
214
|
+
req.add_field("X-PDTP-Peer-Id",@client.my_id)
|
215
|
+
res = Net::HTTP.start(@peer,@port) {|http| http.request(req,body) }
|
216
|
+
|
217
|
+
if res.code == '206' and @method == 'get'
|
218
|
+
#we are the taker
|
219
|
+
@@log.debug("Body Downloaded: url=#{@url} range=#{@byte_range} peer=#{@peer}:#{@port}")
|
220
|
+
info.write(@byte_range.first,res.body)
|
221
|
+
@hash=Digest::SHA256.hexdigest(res.body) rescue nil
|
222
|
+
else
|
223
|
+
raise "HTTP RESPONSE: code=#{res.code} body=#{res.body}"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|