Skip to content

Commit

Permalink
Merge pull request #4 from NickPresta/change-default-hasing-behavior-…
Browse files Browse the repository at this point in the history
…for-body

Refactored hashing to include req. body
  • Loading branch information
nickpresta committed Jan 12, 2015
2 parents 148eeda + d219c80 commit c2ab175
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 119 deletions.
50 changes: 33 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,24 @@ To preseed a request, issue a JSON `POST` request to chameleon at the `_seed` en

Field | Description
----- | -----------
Method | Method is the HTTP method used to match the incoming request. Case insensitive, supports arbitrary methods
URL | URL is the absolute or relative URL to match in requests. Only the path and querystring are used
Body | Body is the raw content
StatusCode | StatusCode is the [HTTP status code](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes)
Headers | Headers is a map of headers in the format of string key to string value
`Request` | Request is the request payload including a URL, Method and Body
`Response` | Response is the response to be cached and sent back for a given request

**Request**

Field | Description
----- | -----------
`Body` | Body is the content for the request. May be empty where body doesn't make sense (e.g. `GET` requests)
`Method` | Method is the HTTP method used to match the incoming request. Case insensitive, supports arbitrary methods
`URL` | URL is the absolute or relative URL to match in requests. Only the path and querystring are used

**Response**

Field | Description
----- | -----------
`Body` | Body is the content for the request. May be empty where body doesn't make sense (e.g. `GET` requests)
`Headers` | Headers is a map of headers in the format of string key to string value
`StatusCode` | StatusCode is the [HTTP status code](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes) of the response

Repeated, duplicate requests to preseed the cache will be discarded and the cache unaffected.

Expand All @@ -67,14 +80,19 @@ Here is an example of preseeding the cache with a JSON response for a `GET` requ
import requests

preseed = json.dumps({
'URL': '/foobar',
'Method': 'GET,
'Body': '{"key": "value"}',
'StatusCode': 200,
'Headers': {
'Content-Type': 'application/json',
'Other-Header': 'something-else',
}
'Request': {
'Body': '',
'URL': '/foobar',
'Method': 'GET',
},
'Response': {
'Body': '{"key": "value"}',
'Headers': {
'Content-Type': 'application/json',
'Other-Header': 'something-else',
},
'StatusCode': 200,
},
})

response = requests.post('http://localhost:6005/_seed', data=preseed)
Expand All @@ -93,14 +111,12 @@ Check out the [example](./example) directory to see preseeding in action.

### How chameleon caches responses

chameleon makes a hash for a given request URI and method and uses that to cache content. What that means:
chameleon makes a hash for a given request URI, request method and request body and uses that to cache content. What that means:

* a request of `GET /foo/` will be cached differently than `GET /bar/`
* a request of `GET /foo/5` will be cached differently than `GET /foo/6`
* a request of `DELETE /foo/5` will be cached differently than `DELETE /foo/6`
* a request of `POST /foo` with a body of `{"hi":"hello}` will be cached the same as a
request of `POST /foo` with a body of `{"spam":"eggs"}`. To get around this, set a header of `chameleon-hash-body`
to any value. This will instruct chameleon to use the entire body as part of the hash.
* a request of `POST /foo` with a body of `{"hi":"hello}` will be cached differently than a request of `POST /foo` with a body of `{"spam":"eggs"}`. To ignore the request body, set a header of `chameleon-no-hash-body` to any value. This will instruct chameleon to ignore the body as part of the hash.

### Writing custom hasher

Expand Down
4 changes: 2 additions & 2 deletions example/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ def do_GET(self):
self.wfile.write(resp.msg.upper())

def do_HASHED(self):
# Custom method that hashes a post with body
self._do_patch_post_put(POST_SERVICE_URL, 'POST', {'chameleon-hash-body': 'true'})
# Custom method that doesn't hash a post with body
self._do_patch_post_put(POST_SERVICE_URL, 'POST', {'chameleon-no-hash-body': 'true'})

def do_SEEDED(self):
url = urlparse.urljoin(SERVICE_URL, self.path[1:])
Expand Down
24 changes: 0 additions & 24 deletions example/testing_data/7806c1446ee40bed04d1d3f1e5c4a206

This file was deleted.

2 changes: 1 addition & 1 deletion example/testing_data/9835adf25e3ecc09431cdf3079bb822a
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"Total-Route-Time": "0",
"User-Agent": "Python-urllib/2.7",
"Via": "1.1 vegur",
"X-Request-Id": "26b789b8-b3a3-451e-8f3f-5b237464c15e"
"X-Request-Id": "4baca827-0052-462a-9995-c65680a1f10d"
},
"json": {
"spam": "eggs"
Expand Down
6 changes: 3 additions & 3 deletions example/testing_data/c884f9c06bdfd2dac66e6af8e2e3c4c1
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
"form": {},
"headers": {
"Accept-Encoding": "identity",
"Connect-Time": "6",
"Connect-Time": "2",
"Connection": "close",
"Content-Length": "14",
"Content-Type": "application/json",
"Host": "httpbin.org",
"Total-Route-Time": "1",
"Total-Route-Time": "0",
"User-Agent": "Python-urllib/2.7",
"Via": "1.1 vegur",
"X-Request-Id": "bea001b0-88c2-49f7-8875-c332a36402ef"
"X-Request-Id": "62f3b7d8-260c-4d6f-934e-40ecac8f9c6a"
},
"json": {
"foo": "bar"
Expand Down
2 changes: 1 addition & 1 deletion example/testing_data/dbc78ad575723d20eb5469356ac19562
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"Total-Route-Time": "0",
"User-Agent": "Python-urllib/2.7",
"Via": "1.1 vegur",
"X-Request-Id": "e8299e3b-866f-49cc-8227-41e1e51ae67e"
"X-Request-Id": "11347282-b40d-47a3-aeb0-e19ba6cc1817"
},
"json": null,
"origin": "99.245.54.15",
Expand Down
2 changes: 1 addition & 1 deletion example/testing_data/f131cff22faf4cb6acd94098b42a9452
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"Total-Route-Time": "0",
"User-Agent": "Python-urllib/2.7",
"Via": "1.1 vegur",
"X-Request-Id": "c99008c6-efcb-4a08-bb6d-47d24fb0bd2e"
"X-Request-Id": "c400e705-56ce-400b-aac0-71a39dd309b0"
},
"json": {
"hi": "hello"
Expand Down
32 changes: 8 additions & 24 deletions example/testing_data/spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"Access-Control-Allow-Origin": "*",
"Content-Length": "0",
"Content-Type": "text/html; charset=utf-8",
"Date": "Sun, 04 Jan 2015 22:29:56 GMT",
"Date": "Mon, 12 Jan 2015 00:42:41 GMT",
"Server": "gunicorn/18.0",
"Via": "1.1 vegur"
}
Expand All @@ -23,7 +23,7 @@
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Origin": "*",
"Content-Length": "135",
"Date": "Sun, 04 Jan 2015 22:29:57 GMT",
"Date": "Mon, 12 Jan 2015 00:42:41 GMT",
"Server": "gunicorn/18.0",
"Via": "1.1 vegur",
"X-More-Info": "http://tools.ietf.org/html/rfc2324"
Expand All @@ -40,7 +40,7 @@
"Access-Control-Allow-Origin": "*",
"Content-Length": "0",
"Content-Type": "text/html; charset=utf-8",
"Date": "Sun, 04 Jan 2015 22:29:57 GMT",
"Date": "Mon, 12 Jan 2015 00:42:41 GMT",
"Server": "gunicorn/18.0",
"Via": "1.1 vegur"
}
Expand All @@ -56,7 +56,7 @@
"Access-Control-Allow-Origin": "*",
"Content-Length": "470",
"Content-Type": "application/json",
"Date": "Sun, 04 Jan 2015 22:29:57 GMT",
"Date": "Mon, 12 Jan 2015 00:42:42 GMT",
"Server": "gunicorn/18.0",
"Via": "1.1 vegur"
}
Expand All @@ -72,7 +72,7 @@
"Access-Control-Allow-Origin": "*",
"Content-Length": "549",
"Content-Type": "application/json",
"Date": "Sun, 04 Jan 2015 22:29:57 GMT",
"Date": "Mon, 12 Jan 2015 00:42:42 GMT",
"Server": "gunicorn/18.0",
"Via": "1.1 vegur"
}
Expand All @@ -88,29 +88,13 @@
"Access-Control-Allow-Origin": "*",
"Content-Length": "546",
"Content-Type": "application/json",
"Date": "Sun, 04 Jan 2015 22:29:58 GMT",
"Date": "Mon, 12 Jan 2015 00:42:42 GMT",
"Server": "gunicorn/18.0",
"Via": "1.1 vegur"
}
},
"key": "c884f9c06bdfd2dac66e6af8e2e3c4c1"
},
{
"response": {
"status_code": 200,
"content": "7806c1446ee40bed04d1d3f1e5c4a206",
"headers": {
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Origin": "*",
"Content-Length": "586",
"Content-Type": "application/json",
"Date": "Sun, 04 Jan 2015 22:29:58 GMT",
"Server": "gunicorn/18.0",
"Via": "1.1 vegur"
}
},
"key": "7806c1446ee40bed04d1d3f1e5c4a206"
},
{
"response": {
"status_code": 200,
Expand All @@ -120,11 +104,11 @@
"Access-Control-Allow-Origin": "*",
"Content-Length": "549",
"Content-Type": "application/json",
"Date": "Sun, 04 Jan 2015 22:29:58 GMT",
"Date": "Mon, 12 Jan 2015 00:42:43 GMT",
"Server": "gunicorn/18.0",
"Via": "1.1 vegur"
}
},
"key": "9835adf25e3ecc09431cdf3079bb822a"
}
]
]
24 changes: 15 additions & 9 deletions example/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,18 @@ def get_name(code):

def preseed(url, method):
payload = json.dumps({
'URL': url,
'Method': method,
'StatusCode': 942,
'Body': '{"key": "value"}',
'Headers': {
'Content-Type': 'application/json',
}
'Request': {
'URL': url,
'Method': method,
'Body': '',
},
'Response': {
'StatusCode': 942,
'Body': '{"key": "value"}',
'Headers': {
'Content-Type': 'application/json',
}
},
})
req = urllib2.Request('http://localhost:{}/_seed'.format(TEST_CHAMELEON_PORT), payload, {'Content-type': 'application/json'})
req.get_method = lambda: 'POST'
Expand Down Expand Up @@ -65,7 +70,7 @@ def test_post_returns_post_body(self):
req.get_method = lambda: 'HASHED'
resp = urllib2.urlopen(req)
parsed = json.loads(resp.read())
self.assertEqual({'post': 'body'}, parsed['json'])
self.assertEqual({'foo': 'bar'}, parsed['json'])

def test_patch_returns_body(self):
url = 'http://localhost:{}/patch'.format(TEST_APP_PORT)
Expand All @@ -91,7 +96,8 @@ def test_delete_returns_200(self):
self.assertEqual(200, resp.getcode())

def test_preseed(self):
preseed('/encoding/utf8', 'GET') # Preseed this URL and Method with some data
resp = preseed('/encoding/utf8', 'GET') # Preseed this URL and Method with some data
self.assertIn(resp.getcode(), (200, 201))
url = 'http://localhost:{}/encoding/utf8'.format(TEST_APP_PORT)
req = urllib2.Request(url)
req.get_method = lambda: 'SEEDED'
Expand Down
31 changes: 20 additions & 11 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ import (
)

type preseedResponse struct {
URL string
Method string
Body string
StatusCode int
Headers map[string]string
Request struct {
Body string
URL string
Method string
}
Response struct {
Body string
StatusCode int
Headers map[string]string
}
}

// PreseedHandler preseeds a Cacher, according to a Hasher
Expand All @@ -32,7 +37,11 @@ func PreseedHandler(cacher Cacher, hasher Hasher) http.HandlerFunc {
return
}

fakeReq, err := http.NewRequest(preseedResp.Method, preseedResp.URL, strings.NewReader(preseedResp.Body))
fakeReq, err := http.NewRequest(
preseedResp.Request.Method,
preseedResp.Request.URL,
strings.NewReader(preseedResp.Request.Body),
)
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)
Expand All @@ -42,17 +51,17 @@ func PreseedHandler(cacher Cacher, hasher Hasher) http.HandlerFunc {
response := cacher.Get(hash)

if response != nil {
log.Printf("-> Proxying [preseeding;cached: %v] to %v\n", hash, preseedResp.URL)
log.Printf("-> Proxying [preseeding;cached: %v] to %v\n", hash, preseedResp.Request.URL)
w.WriteHeader(200)
return
}

log.Printf("-> Proxying [preseeding;not cached: %v] to %v\n", hash, preseedResp.URL)
log.Printf("-> Proxying [preseeding;not cached: %v] to %v\n", hash, preseedResp.Request.URL)

rec := httptest.NewRecorder()
rec.Body = bytes.NewBufferString(preseedResp.Body)
rec.Code = preseedResp.StatusCode
for name, value := range preseedResp.Headers {
rec.Body = bytes.NewBufferString(preseedResp.Response.Body)
rec.Code = preseedResp.Response.StatusCode
for name, value := range preseedResp.Response.Headers {
rec.Header().Set(name, value)
}

Expand Down
Loading

0 comments on commit c2ab175

Please sign in to comment.