1 # Copyright 2012 Jason Woofenden -- GPLv3
4 https = require 'https'
5 async = require 'async'
8 HOST = 'api.appfog.com'
11 # multipart/form-data isn't supposed to be chunk encoded, so we calculate the
12 # length, and generate the textual parts as we go
13 multipart_prepare = (boundary, data, callback) ->
15 # async doesn't seem to work on dictionaries so convert to an array
20 out = "--#{boundary}\r\nContent-Disposition: form-data; name=\"#{k}\""
22 out += "; filename=\"#{v.file}\""
24 if v instanceof Object
26 ct = v['Content-Type']
29 ct = 'application/octet-stream'
31 ct = 'text/plain;charset=UTF-8'
32 out += "Content-Type: #{ct}\r\n\r\n"
34 fs.stat v.file, (err, stat) ->
35 return callback err if err?
36 callback null, text: out, file: v.file, file_length: stat.size
38 callback null, text: out
40 #out += "Content-Type: text/plain;charset=UTF-8\r\n\r\n"
41 #out += "Content-Type: text/plain\r\n\r\n"
44 callback null, text: out
46 return callback err if err
47 parts.push text: "--#{boundary}--"
50 size += part.text.length
52 size += part.file_length
54 callback null, size, parts
56 # encode and print data (as created by multipart_prepare()) to writable stream
58 multipart_write = (req, parts, callback) ->
59 async.forEachSeries parts,
64 file = fs.createReadStream part.file
65 file.on 'error', (err) ->
74 file.pipe req, end: false
81 return callback err if err
84 new_multipart_boundary = ->
85 return "----bmawvch#{Math.floor(Math.random() * 1000000000)}m#{Math.floor(Math.random() * 1000000000)}9rch48dh"
87 request = (method, path, content_type, data, token, callback) ->
93 headers: 'Content-Type': content_type
94 opts.headers['Authorization'] = token if token?
95 opts.headers['Accept'] = '*/*;q=0.8'
98 if content_type is 'multipart/form-data'
99 boundary = new_multipart_boundary()
100 opts.headers['Content-Type'] = "#{content_type}; boundary=#{boundary}"
101 multipart_prepare boundary, data, (err, size, parts) ->
102 return callback err if err
103 opts.headers['Content-Length'] = size
106 opts.headers['Content-Length'] = data?.length ? 0
110 req = https.request opts, (res) ->
111 res.setEncoding 'utf8'
113 res.on 'data', (data) ->
114 response_text += data
118 if res.statusCode isnt 200
119 callback code: res.statusCode, path: path, response: response_text
121 callback null, response_text
122 req.on 'error', (err) ->
126 if content_type is 'multipart/form-data'
127 multipart_write req, data, (err) ->
134 req.write data if data?
139 # try parsing result as json, and fall back to returning string
140 json_request = (method, path, data, token, callback) ->
142 data = JSON.stringify data
143 request method, path, 'application/json', data, token, (err, response) ->
144 return callback err if err
145 return callback null, '' if response is ' '
146 return callback null, '' if response is ''
148 callback null, JSON.parse(response)
150 # some api calls return plain text
151 callback null, response
153 json_get = (path, token, callback) ->
154 json_request 'GET', path, null, token, callback
156 json_post = (path, data, token, callback) ->
157 json_request 'POST', path, data, token, callback
159 json_put = (path, data, token, callback) ->
160 json_request 'PUT', path, data, token, callback
162 exports.login = login = (username, password, callback) ->
163 json_post "/users/#{username}/tokens", {password: password}, null, (err, response) ->
164 return callback err if err
165 if response.token?.length
166 callback null, response.token
168 callback "login: couldn't find the token in the server response: #{JSON.stringify response}"
170 exports.app_update_files = (token, app_name, zip_file, callback) ->
173 "/apps/#{app_name}/application",
174 'multipart/form-data',
180 "Content-Type": 'application/octet-stream'
186 exports.app_set_info = (token, app_name, info, callback) ->
187 json_put "/apps/#{app_name}", info, token, callback
190 return (token, app_name, callback) ->
191 json_get "/apps/#{app_name}#{path}", token, callback
193 exports.app_instances = app_get '/instances'
194 exports.app_crashes = app_get '/crashes'
195 exports.app_info = app_get ''
196 exports.app_logs = app_get '/instances/0/files/logs'
197 exports.app_stdout = app_get '/instances/0/files/logs/stdout.log'
198 exports.app_stderr = app_get '/instances/0/files/logs/stderr.log'
199 exports.app_files = app_get '/instances/0/files/app/app.js'
200 exports.app_stats = app_get '/stats'