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