Skip to content

Instantly share code, notes, and snippets.

@erithmetic
Last active August 15, 2016 13:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erithmetic/e9183ae0f2539c3e6961c824b1865e15 to your computer and use it in GitHub Desktop.
Save erithmetic/e9183ae0f2539c3e6961c824b1865e15 to your computer and use it in GitHub Desktop.
package main
import (
"bufio"
"encoding/json"
"log"
"os"
)
type requestDetails struct {
Path string `json:"path"`
Method string `json:"method"`
Destination string `json:"destination"` // hostname
Scheme string `json:"scheme"`
Query string `json:"query"`
Body string `json:"body"`
Headers map[string][]string `json:"headers"`
}
// Payload structure holds request and response structure
type Payload struct {
Request requestDetails `json:"request"`
ID string `json:"id"`
}
func main() {
// logging to stderr
l := log.New(os.Stderr, "", 0)
s := bufio.NewScanner(os.Stdin)
for s.Scan() {
var payload Payload
err := json.Unmarshal(s.Bytes(), &payload)
if err != nil {
l.Println("Failed to unmarshal payload from hoverfly")
}
if payload.Request.Destination == "api.io" {
payload.Request.Scheme = "https";
}
bts, err := json.Marshal(payload)
if err != nil {
l.Println("Failed to marshal new payload")
}
os.Stdout.Write(bts)
}
}
{
"data":[
{
"response":{
"status":400,
"body":"{\"code\": 400, \"status\": {\"code\": -1204, \"message\": \"Bad request\"}}\n",
"encodedBody":false,
"headers":{
"Cache-Control":[
"max-age=0, no-cache, no-store, must-revalidate"
],
"Content-Length":[
"67"
],
"Content-Type":[
"application/json"
],
"Date":[
"Fri, 12 Aug 2016 18:23:49 GMT"
],
"Etag":[
"\"55930e11-43\""
],
"Hoverfly":[
"Was-Here"
],
"Server":[
"nginx"
],
"X-Content-Type-Options":[
"nosniff"
],
"X-Frame-Options":[
"SAMEORIGIN"
],
"X-Ua-Compatible":[
"IE=Edge"
],
"X-Xss-Protection":[
"1; mode=block"
]
}
},
"request":{
"path":"/andromeda/source",
"method":"POST",
"destination":"api.io",
"scheme":"https",
"query":"username=foo;api_key=bar",
"body":"----------------------------520377437903499113890966\r\nContent-Disposition: form-data; name=\"file\"; filename=\"116-7-12-6-23-12wm010.csv\"\r\nContent-Type: text/csv\r\n\r\nstuff,to,export\r\r1,2,3----------------------------520377437903499113890966\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\nMy Company\r\n----------------------------520377437903499113890966--",
"headers":{
"Connection":[
"close"
],
"Content-Type":[
"multipart/form-data; boundary=--------------------------520377437903499113890966"
]
}
}
}
def use_simulation(name, &blk)
file = File.join(SIMULATION_PATH, "#{name}.json")
if record_mode = File.exist?(file)
puts "Using simulation #{file}"
upload_simulation file
start_simulation
else
puts "No recorded simulation at #{file}, recording from scratch"
start_recording
configure_middleware
end
blk.call
puts "Saving recorded simulation #{file}"
save_simulation file
end
def upload_simulation(file)
RestClient::Request.execute(
method: :get,
url: URI.join(HOVERFLY_API_URL, "/api/records").to_s,
payload: File.read(file)
)
end
def start_simulation
RestClient::Request.execute(
method: :post,
url: URI.join(HOVERFLY_API_URL, "/api/state").to_s,
headers: { 'Content-Type' => 'application/json' },
payload: '{"mode":"simulate"}'
)
end
def start_recording
RestClient::Request.execute(
method: :post,
url: URI.join(HOVERFLY_API_URL, "/api/state").to_s,
headers: { 'Content-Type' => 'application/json' },
payload: '{"mode":"capture"}'
)
end
def configure_middleware
RestClient::Request.execute(
method: :post,
url: URI.join(HOVERFLY_API_URL, "/api/middleware").to_s,
headers: { 'Content-Type' => 'application/json' },
payload: '{"middleware":"/faraday-middleware/rewrite_scheme"}'
)
end
def save_simulation(file)
response = RestClient::Request.execute(
method: :get,
url: URI.join(HOVERFLY_API_URL, "/api/records").to_s
)
File.open file, 'w' do |f|
f.puts response.body
end
end
@benjih
Copy link

benjih commented Aug 12, 2016

Just had a quick look. I think this idea should work. From previous experience working with middleware and HTTPS, you sometimes have to strip some headers from the response. Content-Length has usually caused me issues in the past.

Just so I've got a bit more context, which mode are you using this middleware with?

@erithmetic
Copy link
Author

I've updated the gist with the test code i'm using to show how I'm setting it up to use capture mode, then add middleware

@erithmetic
Copy link
Author

I've also updated my middleware to strip out the Content-Length header to no avail

@erithmetic
Copy link
Author

Also note that I am seeing the requests in the hoverfly logs:

fake_1 | [negroni] Started POST /api/state
fake_1 | {"body":"{\"mode\":\"capture\"}","destination":"","level":"info","msg":"Handling state change request!","newState":"capture","time":"2016-08-12T18:23:35Z"}
fake_1 | [negroni] Completed 200 OK in 149.533µs
fake_1 | [negroni] Started POST /api/middleware
fake_1 | [negroni] Completed 200 OK in 1.326917ms
fake_1 | {"destination":"api.io","level":"info","method":"POST","middleware":"/faraday-middleware/rewrite_scheme","mode":"capture","msg":"request and response captured","path":"/andromeda/source","rawQuery":"username=foo;api_key=bar","time":"2016-08-12T18:23:48Z"}
fake_1 | {"destination":"api.io","level":"info","method":"POST","middleware":"/faraday-middleware/rewrite_scheme","mode":"capture","msg":"request and response captured","path":"/andromeda/source","rawQuery":"username=foo;api_key=bar","time":"2016-08-12T18:23:48Z"}
fake_1 | {"destination":"api.io","level":"info","method":"POST","middleware":"/faraday-middleware/rewrite_scheme","mode":"capture","msg":"request and response captured","path":"/andromeda/source","rawQuery":"username=foo;api_key=bar","time":"2016-08-12T18:23:49Z"}
fake_1 | {"destination":"api.io","level":"info","method":"POST","middleware":"/faraday-middleware/rewrite_scheme","mode":"capture","msg":"request and response captured","path":"/andromeda/source","rawQuery":"username=foo;api_key=bar","time":"2016-08-12T18:23:49Z"}
fake_1 | [negroni] Started GET /api/records
fake_1 | [negroni] Completed 200 OK in 641.116µs

@benjih
Copy link

benjih commented Aug 15, 2016

Hey @dkastner,

I was able to reproduce the error using Nodejs with Hoverfly.

Error: self signed certificate in certificate chain
    at Error (native)
    at TLSSocket.<anonymous> (_tls_wrap.js:1060:38)
    at emitNone (events.js:86:13)
    at TLSSocket.emit (events.js:185:7)
    at TLSSocket._finishInit (_tls_wrap.js:584:8)
    at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:416:38)

Having spent some time thinking about it, I don't think producing middleware to terminate the SSL at Hoverfly is the best thing to do.

The best working solution we have so far is by changing the NODE_TLS_REJECT_UNAUTHORIZED to false so that Nodejs ignores the self-signed certificate.

process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

We acknowledge that this isn't ideal but likewise, we can't really give out a signed public and private set of keys. It might be possible to generate a set of keys using Let's Encrypt and then giving them both to Hoverfly, though this isn't something we've done yet.

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