require 'webrick' require 'shuck/file_store' require 'shuck/xml_adapter' module Shuck class Request CREATE_BUCKET = "CREATE_BUCKET" STORE = "STORE" COPY = "COPY" GET = "GET" MOVE = "MOVE" DELETE = "DELETE" attr_accessor :bucket,:object,:type,:src_bucket,:src_object,:method,:webrick_request,:path def inspect puts "-----Inspect Shuck Request" puts "Type: #{@type}" puts "Request Method: #{@method}" puts "Bucket: #{@bucket}" puts "Object: #{@object}" puts "Src Bucket: #{@src_bucket}" puts "Src Object: #{@src_object}" puts "-----Done" end end class Servlet < WEBrick::HTTPServlet::AbstractServlet def initialize(server,store,hostname) super(server) @store = store @hostname = hostname end def do_GET(request, response) path = request.path path_len = path.size host = request["HOST"] path_style_request = true bucket = nil if host != @hostname bucket = host.split(".")[0] path_style_request = false end if path == "/" and path_style_request response.status = 200 response['Content-Type'] = 'text/xml' buckets = @store.buckets response.body = XmlAdapter.buckets(buckets) else if path_style_request elems = path[1,path_len].split("/") bucket = elems[0] else elems = path.split("/") end if elems.size == 0 # List buckets buckets = @store.buckets response.status = 200 response.body = XmlAdapter.buckets(buckets) response['Content-Type'] = "application/xml" elsif elems.size == 1 bucket_obj = @store.get_bucket(bucket) if bucket_obj response.status = 200 response.body = XmlAdapter.bucket(bucket_obj) response['Content-Type'] = "application/xml" else response.status = 404 response.body = XmlAdapter.error_no_such_bucket(bucket) response['Content-Type'] = "application/xml" end else object = elems[1,elems.size].join('/') real_obj = @store.get_object(bucket,object, request) if real_obj response.status = 200 response['Content-Type'] = real_obj.content_type content_length = File::Stat.new(real_obj.io.path).size response['Etag'] = real_obj.md5 response['Accept-Ranges'] = "bytes" # Added Range Query support if range = request.header["range"].first response.status = 206 if range =~ /bytes=(\d*)-(\d*)/ start = $1.to_i finish = $2.to_i finish_str = "" if finish == 0 finish = content_length - 1 finish_str = "#{finish}" else finish_str = finish.to_s end bytes_to_read = finish - start + 1 response['Content-Range'] = "bytes #{start}-#{finish_str}/#{content_length}" real_obj.io.pos = start response.body = real_obj.io.read(bytes_to_read) return end end response['Content-Length'] = File::Stat.new(real_obj.io.path).size response.body = real_obj.io else response.status = 404 response.body = "" end end end end def do_PUT(request,response) s_req = normalize_request(request) case s_req.type when Request::COPY @store.copy_object(s_req.src_bucket,s_req.src_object,s_req.bucket,s_req.object) when Request::STORE real_obj = @store.store_object(s_req.bucket,s_req.object,s_req.webrick_request) response['Etag'] = real_obj.md5 when Request::CREATE_BUCKET @store.create_bucket(s_req.bucket) end response.status = 200 response.body = "" response['Content-Type'] = "text/xml" end def do_POST(request,response) p request end def do_DELETE(request,response) p request end private # This method takes a webrick request and generates a normalized Shuck request def normalize_request(webrick_r) path = webrick_r.path path_len = path.size path_style_request = true host = webrick_r["Host"] bucket = nil object = nil path_style_request = true if host != @hostname bucket = host.split(".")[0] path_style_request = false end req = Request.new req.path = webrick_r.path if path == "/" if bucket req.type = Request::CREATE_BUCKET end else if path_style_request elems = path[1,path_len].split("/") bucket = elems[0] if elems.size == 1 req.type = Request::CREATE_BUCKET else req.type = Request::STORE object = elems[1,elems.size].join('/') end else req.type = Request::STORE object = webrick_r.path end end copy_source = webrick_r.header["x-amz-copy-source"] if copy_source and copy_source.size == 1 src_elems = copy_source.first.split("/") root_offset = src_elems[0] == "" ? 1 : 0 req.src_bucket = src_elems[root_offset] req.src_object = src_elems[1 + root_offset,src_elems.size].join("/") req.type = Request::COPY end req.bucket = bucket req.object = object req.webrick_request = webrick_r return req end def dump_request(request) puts "----------Dump Request-------------" puts request.request_method puts request.path request.each do |k,v| puts "#{k}:#{v}" end puts "----------End Dump -------------" end end class Server def initialize(port,store,hostname = "localhost") @port = port @store = store @hostname = hostname end def serve @server = WEBrick::HTTPServer.new(:Port => @port) @server.mount "/", Servlet, @store,@hostname trap "INT" do @server.shutdown end @server.start end def shutdown @server.shutdown end end end