Skip to content

Commit 86a3e2b

Browse files
committed
add support for full har
add support for multiple enteries per har fix curl multipart fix httpie parameterized post fix ocaml source variable
1 parent 612d1d2 commit 86a3e2b

6 files changed

Lines changed: 189 additions & 146 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Options:
4141

4242
###### Example
4343

44-
process single file: [`example.json`](test/fixtures/requests/full.json) from [HAR Request Object](http://www.softwareishard.com/blog/har-12-spec/#request) format:
44+
process single file: [`example.json`](test/fixtures/requests/full.json) in [HAR Request Object](http://www.softwareishard.com/blog/har-12-spec/#request) format, or full [HAR](http://www.softwareishard.com/blog/har-12-spec/#log) log format:
4545

4646
```shell
4747
httpsnippet example.json --target node --client unirest --output ./snippets
@@ -159,7 +159,7 @@ console.log(snippet.convert('node', 'unirest'));
159159

160160
## Documentation
161161

162-
At the heart of this module is the [HAR Request object](http://www.softwareishard.com/blog/har-12-spec/#request) as the HTTP request description format, please review some of the sample JSON HAR Request objects in [test fixtures](/test/fixtures/requests), or read the [HAR Docs](http://www.softwareishard.com/blog/har-12-spec/#request) for more details.
162+
At the heart of this module is the [HAR Format](http://www.softwareishard.com/blog/har-12-spec/#request) as the HTTP request description format, please review some of the sample JSON HAR Request objects in [test fixtures](/test/fixtures/requests), or read the [HAR Docs](http://www.softwareishard.com/blog/har-12-spec/#request) for more details.
163163

164164
For detailed information on each target, please review the [wiki](https://github.com/Mashape/httpsnippet/wiki).
165165

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"scripts": {
4646
"test": "standard && mocha --no-timeouts --reporter spec --fgrep 'Request Validation' --invert",
4747
"travis": "standard && mocha --no-timeouts --reporter spec",
48-
"coverage": "istanbul cover ./node_modules/mocha/bin/_mocha",
48+
"coverage": "istanbul cover ./node_modules/mocha/bin/_mocha -- --fgrep 'Request Validation' --invert",
4949
"prepush": "npm test"
5050
},
5151
"standard": {

src/index.js

Lines changed: 150 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -11,155 +11,181 @@ var util = require('util')
1111
var validate = require('har-validator')
1212

1313
// constructor
14-
var HTTPSnippet = function (req, lang) {
15-
this.source = util._extend({}, req)
16-
17-
// add optional properties to make validation successful
18-
this.source.httpVersion = this.source.httpVersion || 'HTTP/1.1'
19-
this.source.queryString = this.source.queryString || []
20-
this.source.headers = this.source.headers || []
21-
this.source.cookies = this.source.cookies || []
22-
this.source.postData = this.source.postData || {}
23-
this.source.postData.mimeType = this.source.postData.mimeType || 'application/octet-stream'
24-
25-
this.source.bodySize = 0
26-
this.source.headersSize = 0
27-
this.source.postData.size = 0
28-
29-
validate.request(this.source, function (err, valid) {
30-
if (!valid) {
31-
throw err
32-
}
33-
34-
// construct query string object
35-
this.source.queryObj = {}
36-
this.source.headersObj = {}
37-
this.source.cookiesObj = {}
38-
this.source.allHeaders = {}
39-
this.source.postData.jsonObj = false
40-
this.source.postData.paramsObj = false
14+
var HTTPSnippet = function (data, lang) {
15+
var entries
16+
var self = this
17+
var input = util._extend({}, data)
18+
19+
// prep the main container
20+
self.requests = []
21+
22+
// is it har?
23+
if (input.log && input.log.entries) {
24+
entries = input.log.entries
25+
} else {
26+
entries = [{
27+
request: input
28+
}]
29+
}
4130

42-
// construct query objects
43-
if (this.source.queryString && this.source.queryString.length) {
44-
debug('queryString found, constructing queryString pair map')
31+
entries.map(function (entry) {
32+
// add optional properties to make validation successful
33+
entry.request.httpVersion = entry.request.httpVersion || 'HTTP/1.1'
34+
entry.request.queryString = entry.request.queryString || []
35+
entry.request.headers = entry.request.headers || []
36+
entry.request.cookies = entry.request.cookies || []
37+
entry.request.postData = entry.request.postData || {}
38+
entry.request.postData.mimeType = entry.request.postData.mimeType || 'application/octet-stream'
4539

46-
this.source.queryObj = this.source.queryString.reduce(helpers.reducer, {})
47-
}
40+
entry.request.bodySize = 0
41+
entry.request.headersSize = 0
42+
entry.request.postData.size = 0
4843

49-
// construct headers objects
50-
if (this.source.headers && this.source.headers.length) {
51-
// loweCase header keys
52-
this.source.headersObj = this.source.headers.reduceRight(function (headers, header) {
53-
headers[header.name.toLowerCase()] = header.value
54-
return headers
55-
}, {})
56-
}
44+
validate.request(entry.request, function (err, valid) {
45+
if (!valid) {
46+
debug(err)
5747

58-
// construct headers objects
59-
if (this.source.cookies && this.source.cookies.length) {
60-
this.source.cookiesObj = this.source.cookies.reduceRight(function (cookies, cookie) {
61-
cookies[cookie.name] = cookie.value
62-
return cookies
63-
}, {})
64-
}
48+
throw err
49+
}
6550

66-
// construct Cookie header
67-
var cookies = this.source.cookies.map(function (cookie) {
68-
return encodeURIComponent(cookie.name) + '=' + encodeURIComponent(cookie.value)
51+
self.requests.push(self.prepare(entry.request))
6952
})
53+
})
54+
}
7055

71-
if (cookies.length) {
72-
this.source.allHeaders.cookie = cookies.join('; ')
73-
}
56+
HTTPSnippet.prototype.prepare = function (request) {
57+
// construct utility properties
58+
request.queryObj = {}
59+
request.headersObj = {}
60+
request.cookiesObj = {}
61+
request.allHeaders = {}
62+
request.postData.jsonObj = false
63+
request.postData.paramsObj = false
64+
65+
// construct query objects
66+
if (request.queryString && request.queryString.length) {
67+
debug('queryString found, constructing queryString pair map')
68+
69+
request.queryObj = request.queryString.reduce(helpers.reducer, {})
70+
}
7471

75-
switch (this.source.postData.mimeType) {
76-
case 'multipart/mixed':
77-
case 'multipart/related':
78-
case 'multipart/form-data':
79-
case 'multipart/alternative':
80-
// reset values
81-
this.source.postData.text = ''
82-
this.source.postData.mimeType = 'multipart/form-data'
72+
// construct headers objects
73+
if (request.headers && request.headers.length) {
74+
// loweCase header keys
75+
request.headersObj = request.headers.reduceRight(function (headers, header) {
76+
headers[header.name.toLowerCase()] = header.value
77+
return headers
78+
}, {})
79+
}
80+
81+
// construct headers objects
82+
if (request.cookies && request.cookies.length) {
83+
request.cookiesObj = request.cookies.reduceRight(function (cookies, cookie) {
84+
cookies[cookie.name] = cookie.value
85+
return cookies
86+
}, {})
87+
}
8388

89+
// construct Cookie header
90+
var cookies = request.cookies.map(function (cookie) {
91+
return encodeURIComponent(cookie.name) + '=' + encodeURIComponent(cookie.value)
92+
})
93+
94+
if (cookies.length) {
95+
request.allHeaders.cookie = cookies.join('; ')
96+
}
97+
98+
switch (request.postData.mimeType) {
99+
case 'multipart/mixed':
100+
case 'multipart/related':
101+
case 'multipart/form-data':
102+
case 'multipart/alternative':
103+
// reset values
104+
request.postData.text = ''
105+
request.postData.mimeType = 'multipart/form-data'
106+
107+
if (request.postData.params) {
84108
var form = new MultiPartForm()
85109

86110
// easter egg
87111
form._boundary = '---011000010111000001101001'
88112

89-
this.source.postData.params.map(function (param) {
113+
request.postData.params.map(function (param) {
90114
form.append(param.name, param.value || '', {
91115
filename: param.fileName || null,
92116
contentType: param.contentType || null
93117
})
94118
})
95119

96120
form.pipe(es.map(function (data, cb) {
97-
this.source.postData.text += data
98-
}.bind(this)))
99-
100-
this.source.postData.boundary = form.getBoundary()
101-
this.source.headersObj['content-type'] = 'multipart/form-data; boundary=' + form.getBoundary()
102-
break
103-
104-
case 'application/x-www-form-urlencoded':
105-
if (!this.source.postData.params) {
106-
this.source.postData.text = ''
107-
} else {
108-
this.source.postData.paramsObj = this.source.postData.params.reduce(helpers.reducer, {})
109-
110-
// always overwrite
111-
this.source.postData.text = qs.stringify(this.source.postData.paramsObj)
112-
}
113-
break
114-
115-
case 'text/json':
116-
case 'text/x-json':
117-
case 'application/json':
118-
case 'application/x-json':
119-
this.source.postData.mimeType = 'application/json'
120-
121-
if (this.source.postData.text) {
122-
try {
123-
this.source.postData.jsonObj = JSON.parse(this.source.postData.text)
124-
} catch (e) {
125-
debug(e)
126-
127-
// force back to text/plain
128-
// if headers have proper content-type value, then this should also work
129-
this.source.postData.mimeType = 'text/plain'
130-
}
121+
request.postData.text += data
122+
}))
123+
124+
request.postData.boundary = form.getBoundary()
125+
request.headersObj['content-type'] = 'multipart/form-data; boundary=' + form.getBoundary()
126+
}
127+
break
128+
129+
case 'application/x-www-form-urlencoded':
130+
if (!request.postData.params) {
131+
request.postData.text = ''
132+
} else {
133+
request.postData.paramsObj = request.postData.params.reduce(helpers.reducer, {})
134+
135+
// always overwrite
136+
request.postData.text = qs.stringify(request.postData.paramsObj)
137+
}
138+
break
139+
140+
case 'text/json':
141+
case 'text/x-json':
142+
case 'application/json':
143+
case 'application/x-json':
144+
request.postData.mimeType = 'application/json'
145+
146+
if (request.postData.text) {
147+
try {
148+
request.postData.jsonObj = JSON.parse(request.postData.text)
149+
} catch (e) {
150+
debug(e)
151+
152+
// force back to text/plain
153+
// if headers have proper content-type value, then this should also work
154+
request.postData.mimeType = 'text/plain'
131155
}
132-
break
133-
}
156+
}
157+
break
158+
}
134159

135-
// create allHeaders object
136-
this.source.allHeaders = util._extend(this.source.allHeaders, this.source.headersObj)
160+
// create allHeaders object
161+
request.allHeaders = util._extend(request.allHeaders, request.headersObj)
137162

138-
// deconstruct the uri
139-
this.source.uriObj = url.parse(this.source.url, true, true)
163+
// deconstruct the uri
164+
request.uriObj = url.parse(request.url, true, true)
140165

141-
// merge all possible queryString values
142-
this.source.queryObj = util._extend(this.source.queryObj, this.source.uriObj.query)
166+
// merge all possible queryString values
167+
request.queryObj = util._extend(request.queryObj, request.uriObj.query)
143168

144-
// reset uriObj values for a clean url
145-
this.source.uriObj.query = null
146-
this.source.uriObj.search = null
147-
this.source.uriObj.path = this.source.uriObj.pathname
169+
// reset uriObj values for a clean url
170+
request.uriObj.query = null
171+
request.uriObj.search = null
172+
request.uriObj.path = request.uriObj.pathname
148173

149-
// keep the base url clean of queryString
150-
this.source.url = url.format(this.source.uriObj)
174+
// keep the base url clean of queryString
175+
request.url = url.format(request.uriObj)
151176

152-
// update the uri object
153-
this.source.uriObj.query = this.source.queryObj
154-
this.source.uriObj.search = qs.stringify(this.source.queryObj)
177+
// update the uri object
178+
request.uriObj.query = request.queryObj
179+
request.uriObj.search = qs.stringify(request.queryObj)
155180

156-
if (this.source.uriObj.search) {
157-
this.source.uriObj.path = this.source.uriObj.pathname + '?' + this.source.uriObj.search
158-
}
181+
if (request.uriObj.search) {
182+
request.uriObj.path = request.uriObj.pathname + '?' + request.uriObj.search
183+
}
159184

160-
// construct a full url
161-
this.source.fullUrl = url.format(this.source.uriObj)
162-
}.bind(this))
185+
// construct a full url
186+
request.fullUrl = url.format(request.uriObj)
187+
188+
return request
163189
}
164190

165191
HTTPSnippet.prototype.convert = function (target, client, opts) {
@@ -170,7 +196,11 @@ HTTPSnippet.prototype.convert = function (target, client, opts) {
170196
var func = this._matchTarget(target, client)
171197

172198
if (func) {
173-
return func.call(this, this.source, opts)
199+
var results = this.requests.map(function (request) {
200+
return func.call(null, request, opts)
201+
})
202+
203+
return results.length === 1 ? results[0] : results
174204
}
175205

176206
return false

src/targets/ocaml/cohttp.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ module.exports = function (source, options) {
5252
code.push(util.format('Client.call %s%s%s uri',
5353
headers.length ? '~headers ' : '',
5454
source.postData.text ? '~body ' : '',
55-
(methods.indexOf(this.source.method.toLowerCase()) >= 0 ? ('`' + this.source.method.toUpperCase()) : '(Code.method_of_string "' + this.source.method + '")')
55+
(methods.indexOf(source.method.toLowerCase()) >= 0 ? ('`' + source.method.toUpperCase()) : '(Code.method_of_string "' + source.method + '")')
5656
))
5757

5858
// Catch result

src/targets/shell/curl.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,24 @@ module.exports = function (source, options) {
3939
code.push(util.format('%s %s', opts.short ? '-b' : '--cookie', helpers.quote(source.allHeaders.cookie)))
4040
}
4141

42-
// request body
43-
if (source.postData.text) {
44-
code.push(util.format('%s %s', opts.short ? '-d' : '--data', helpers.escape(helpers.quote(source.postData.text))))
45-
}
46-
4742
// construct post params
48-
if (!source.postData.text && source.postData.params) {
49-
source.postData.params.map(function (param) {
50-
var post = util.format('%s=%s', param.name, param.value)
51-
code.push(util.format('%s %s', opts.short ? '-F' : '--form', helpers.quote(post)))
52-
})
43+
switch (source.postData.mimeType) {
44+
case 'multipart/form-data':
45+
source.postData.params.map(function (param) {
46+
if (param.fileName && !param.value) {
47+
param.value = '@' + param.fileName
48+
}
49+
50+
var post = util.format('%s=%s', param.name, param.value)
51+
code.push(util.format('%s %s', opts.short ? '-F' : '--form', helpers.quote(post)))
52+
})
53+
break
54+
55+
default:
56+
// raw request body
57+
if (source.postData.text) {
58+
code.push(util.format('%s %s', opts.short ? '-d' : '--data', helpers.escape(helpers.quote(source.postData.text))))
59+
}
5360
}
5461

5562
return code.join()

0 commit comments

Comments
 (0)