Skip to content

Instantly share code, notes, and snippets.

@mpage
Created January 18, 2012 05:53
Show Gist options
  • Save mpage/1631338 to your computer and use it in GitHub Desktop.
Save mpage/1631338 to your computer and use it in GitHub Desktop.
require 'fileutils'
require 'net/http'
require 'uri'
require 'vcap/common'
module VCAP
module Stager
end
end
module VCAP::Stager::Util
class HttpStatusError < StandardError; end
class IOBuffer
attr_reader :size
def initialize(*ios)
@ios = ios.dup
@io_off = 0
@stream_off = 0
@size = @ios.reduce(0) {|sum, io| sum + io.size }
end
def read(nbytes_left=nil, dst=nil)
ret = nil
nbytes_left ||= @size - @stream_off
while (@io_off < @ios.length) && (nbytes_left > 0)
cur_io = @ios[@io_off]
tbuf = cur_io.read(nbytes_left)
if tbuf == nil
# EOF encountered, no bytes read. This can happen
# if a previous read read exactly the number of bytes remaining for the io.
@io_off += 1
else
if ret
ret += tbuf
else
ret = tbuf
end
# EOF encountered after reading > 0 bytes.
@io_off += 1 if tbuf.length < nbytes_left
@stream_off += tbuf.length
nbytes_left -= tbuf.length
end
end
dst += ret if dst
ret
end
def rewind
@io_off = 0
@stream_off = 0
@ios.each {|io| io.rewind }
end
end
class MultipartFileStream
attr_reader :boundary, :size, :header, :footer
def initialize(fieldname, file)
@boundary = VCAP.secure_uuid
@fieldname = fieldname
@header = make_header(@boundary, fieldname, File.basename(file.path))
@footer = make_footer(@boundary)
@io_buf = VCAP::Stager::Util::IOBuffer.new(@header, file, @footer)
@size = @io_buf.size
end
def read(*args)
@io_buf.read(*args)
end
def size
@io_buf.size
end
private
def make_header(boundary, fieldname, filename)
hdr = "--#{boundary}\r\n"
hdr += "Content-Disposition: form-data; name=\"#{fieldname}\"; filename=\"#{filename}\"\r\n"
hdr += "Content-Type: application/octet-stream\r\n"
hdr += "\r\n"
StringIO.new(hdr)
end
def make_footer(boundary)
StringIO.new("\r\n--#{boundary}--\r\n")
end
end
class << self
# Uploads the file living at _droplet_path_ to the uri using a PUT request.
#
# NB: This streams the file off of disk.
#
# @param uri String Where the app should be uploaded to
# @param droplet_path String The location on disk of the droplet to be uploaded
def upload_droplet(droplet_uri, droplet_path)
uri = URI.parse(droplet_uri)
req = make_request(Net::HTTP::Post, uri)
droplet = File.open(droplet_path)
mpfs = MultipartFileStream.new('upload[droplet]', droplet)
req.content_length = mpfs.size
req.set_content_type('multipart/form-data', :boundary => mpfs.boundary)
req.body_stream = mpfs
ret = Net::HTTP.start(uri.host, uri.port) do |http|
http.request(req)
end
# Throws if non-200
ret.value
ensure
droplet.close if droplet
end
private
# Creates an instance of a class derived from Net::HTTPRequest
# and sets basic auth info
#
# @param klass Net::HTTPRequest
# @param uri URI::HTTP
#
# @return Net::HTTPRequest
def make_request(klass, uri)
req = klass.new(uri.request_uri)
if uri.user
req.basic_auth(uri.user, uri.password)
end
req
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment