Branching mogilefs-client to version 1.2.1
[ruby-mogilefs-client.git] / lib / mogilefs / httpfile.rb
blob13788aaf5878f4b3af2d234104490017add2d687
1 require 'fcntl'
2 require 'socket'
3 require 'stringio'
4 require 'uri'
5 require 'mogilefs/backend'
7 ##
8 # HTTPFile wraps up the new file operations for storing files onto an HTTP
9 # storage node.
11 # You really don't want to create an HTTPFile by hand.  Instead you want to
12 # create a new file using MogileFS::MogileFS.new_file.
14 # WARNING! HTTP mode is completely untested as I cannot make it work on
15 # FreeBSD.  Please send patches/tests if you find bugs.
16 #--
17 # TODO dup'd content in MogileFS::NFSFile
19 class MogileFS::HTTPFile < StringIO
21   ##
22   # The path this file will be stored to.
24   attr_reader :path
26   ##
27   # The key for this file.  This key won't represent a real file until you've
28   # called #close.
30   attr_reader :key
32   ##
33   # The class of this file.
35   attr_reader :class
37   ##
38   # Works like File.open.  Use MogileFS::MogileFS#new_file instead of this
39   # method.
41   def self.open(*args)
42     fp = new(*args)
44     return fp unless block_given?
46     begin
47       yield fp
48     ensure
49       fp.close
50     end
51   end
53   ##
54   # Creates a new HTTPFile with MogileFS-specific data.  Use
55   # MogileFS::MogileFS#new_file instead of this method.
57   def initialize(mg, fid, path, devid, klass, key, dests, content_length)
58     super ''
59     @mg = mg
60     @fid = fid
61     @path = path
62     @devid = devid
63     @klass = klass
64     @key = key
66     @dests = dests.map { |(_,u)| URI.parse u }
67     @tried = {}
69     @socket = nil
70   end
72   ##
73   # Closes the file handle and marks it as closed in MogileFS.
75   def close
76     connect_socket
78     @socket.write "PUT #{@path.request_uri} HTTP/1.0\r\nContent-length: #{length}\r\n\r\n#{string}"
80     if connected? then
81       line = @socket.gets
82       raise 'Unable to read response line from server' if line.nil?
84       if line =~ %r%^HTTP/\d+\.\d+\s+(\d+)% then
85         status = Integer $1
86         case status
87         when 200..299 then # success!
88         else
89           found_header = false
90           body = []
91           line = @socket.gets
92           until line.nil? do
93             line.strip
94             found_header = true if line.nil?
95             next unless found_header
96             body << " #{line}"
97           end
98           body = body[0, 512] if body.length > 512
99           raise "HTTP response status from upload: #{body}"
100         end
101       else
102         raise "Response line not understood: #{line}"
103       end
104       @socket.close
105     end
107     @mg.backend.create_close(:fid => @fid, :devid => @devid,
108                              :domain => @mg.domain, :key => @key,
109                              :path => @path, :size => length)
110     return nil
111   end
113   private
115   def connected?
116     return !(@socket.nil? or @socket.closed?)
117   end
119   def connect_socket
120     return @socket if connected?
122     next_path
124     if @path.nil? then
125       @tried.clear
126       next_path
127       raise 'Unable to open socket to storage node' if @path.nil?
128     end
130     @socket = TCPSocket.new @path.host, @path.port
131   end
133   def next_path
134     @path = nil
135     @dests.each do |dest|
136       unless @tried.include? dest then
137         @path = dest
138         return
139       end
140     end
141   end