Skip to content

Instantly share code, notes, and snippets.

@turtlesoupy
Created November 8, 2011 07:13
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save turtlesoupy/1347203 to your computer and use it in GitHub Desktop.
Save turtlesoupy/1347203 to your computer and use it in GitHub Desktop.
Robust node.js s3 put
fs = require 'fs'
http = require 'http'
https = require 'https'
crypto = require 'crypto'
mime = require 'mime'
xml2js = require 'xml2js'
delayTimeout = (ms, func) -> setTimeout func, ms
class @S3Put
constructor: (@awsKey, @awsSecret, @bucket, @secure=true, @timeout=60*1000) ->
put: (filePath, resource, headers, callback) ->
amzHeaders = {}
(amzHeaders[k] = v for k,v of headers when k.indexOf("x-amz") == 0)
mimeType = mime.lookup(filePath)
fs.stat filePath, (err, stats) =>
return callback(err) if err?
contentLength = stats.size
md5Hash = crypto.createHash 'md5'
rs = fs.ReadStream(filePath)
rs.on 'data', (d) -> md5Hash.update(d)
rs.on 'end', =>
md5 = md5Hash.digest('base64')
date = new Date()
httpOptions =
host: "s3.amazonaws.com"
path: "/#{@bucket}/#{resource}"
headers:
"Authorization": "AWS #{@awsKey}:#{@sign(resource, md5, mimeType, date, amzHeaders)}"
"Date": date.toUTCString()
"Content-Length": contentLength
"Content-Type": mimeType
"Content-MD5": md5
"Expect": "100-continue"
method: "PUT"
(httpOptions.headers[k] = v for k,v of headers)
timeout = null
req = (if @secure then https else http).request httpOptions, (res) =>
if res.statusCode == 200
clearTimeout(timeout)
headers = JSON.stringify(res.headers)
return callback(null, {headers: headers, code: res.statusCode})
responseBody = ""
res.setEncoding("utf8")
res.on "data", (chunk) ->
responseBody += chunk
res.on "end", ->
parser = new xml2js.Parser()
parser.parseString responseBody, (err, result) ->
return callback(err) if err?
return callback(result)
timeout = delayTimeout @timeout, =>
req.abort()
return callback({message: "Timed out after #{@timeout}ms"})
req.on "continue", ->
rs2 = fs.ReadStream(filePath)
rs2.on 'error', callback
rs2.pipe(req)
sign: (resource, md5, contentType, date, amzHeaders) ->
data = ["PUT", md5, contentType, date.toUTCString(), @canonicalHeaders(amzHeaders).join("\n"), "/#{@bucket}/#{resource}"].join("\n")
crypto.createHmac('sha1', @awsSecret).update(data).digest('base64')
canonicalHeaders: (headers) ->
("#{k.toLowerCase()}:#{v}" for k,v of headers).sort()
@thomasdavis
Copy link

Should push this to npm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment