From 6287af8186fa44f9d1ca581a6b37bdff701f3287 Mon Sep 17 00:00:00 2001 From: Steven Honson Date: Mon, 20 Aug 2012 23:35:03 +1000 Subject: [PATCH 1/9] New delete cookie endpoint. --- README.md | 1 + httpbin/core.py | 12 ++++++++++++ httpbin/templates/httpbin.1.html | 1 + 3 files changed, 14 insertions(+) diff --git a/README.md b/README.md index df6687f..238118e 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Freely hosted in [HTTP](http://httpbin.org) & - [`/relative-redirect/:n`](http://httpbin.org/relative-redirect/6) 302 Relative redirects *n* times. - [`/cookies`](http://httpbin.org/cookies) Returns cookie data. - [`/cookies/set?name=value`](http://httpbin.org/cookies/set?k1=v1&k2=v2) Sets one or more simple cookies. +- [`/cookies/delete?name`](http://httpbin.org/cookies/delete?k1) Deletes one or more simple cookies. - [`/basic-auth/:user/:passwd`](http://httpbin.org/basic-auth/user/passwd) Challenges HTTPBasic Auth. - [`/hidden-basic-auth/:user/:passwd`](http://httpbin.org/hidden-basic-auth/user/passwd) 404'd BasicAuth. - [`/digest-auth/:qop/:user/:passwd`](http://httpbin.org/digest-auth/auth/user/passwd) Challenges HTTP Digest Auth. diff --git a/httpbin/core.py b/httpbin/core.py index c13f339..d3ec9a6 100644 --- a/httpbin/core.py +++ b/httpbin/core.py @@ -271,6 +271,18 @@ def set_cookies(): return r +@app.route('/cookies/delete') +def delete_cookies(): + """Deletes cookie(s) as provided by the query string and redirects to cookie list.""" + + cookies = dict(request.args.items()) + r = app.make_response(redirect('/cookies')) + for key, value in cookies.items(): + r.delete_cookie(key=key) + + return r + + @app.route('/basic-auth//') def basic_auth(user='user', passwd='passwd'): """Prompts the user for authorization using HTTP Basic Auth.""" diff --git a/httpbin/templates/httpbin.1.html b/httpbin/templates/httpbin.1.html index eae602d..5241248 100644 --- a/httpbin/templates/httpbin.1.html +++ b/httpbin/templates/httpbin.1.html @@ -21,6 +21,7 @@
  • /relative-redirect/:n 302 Relative redirects n times.
  • /cookies Returns cookie data.
  • /cookies/set?name=value Sets one or more simple cookies.
  • +
  • /cookies/delete?name Deletes one or more simple cookies.
  • /basic-auth/:user/:passwd Challenges HTTPBasic Auth.
  • /hidden-basic-auth/:user/:passwd 404'd BasicAuth.
  • /digest-auth/:qop/:user/:passwd Challenges HTTP Digest Auth.
  • From 08f8f0b516d44818977662da501d600d4f0c528b Mon Sep 17 00:00:00 2001 From: Steven Honson Date: Mon, 20 Aug 2012 23:40:45 +1000 Subject: [PATCH 2/9] Updated example linked to include two deletions. --- README.md | 2 +- httpbin/templates/httpbin.1.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 238118e..bb0ecb2 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Freely hosted in [HTTP](http://httpbin.org) & - [`/relative-redirect/:n`](http://httpbin.org/relative-redirect/6) 302 Relative redirects *n* times. - [`/cookies`](http://httpbin.org/cookies) Returns cookie data. - [`/cookies/set?name=value`](http://httpbin.org/cookies/set?k1=v1&k2=v2) Sets one or more simple cookies. -- [`/cookies/delete?name`](http://httpbin.org/cookies/delete?k1) Deletes one or more simple cookies. +- [`/cookies/delete?name`](http://httpbin.org/cookies/delete?k1&k2) Deletes one or more simple cookies. - [`/basic-auth/:user/:passwd`](http://httpbin.org/basic-auth/user/passwd) Challenges HTTPBasic Auth. - [`/hidden-basic-auth/:user/:passwd`](http://httpbin.org/hidden-basic-auth/user/passwd) 404'd BasicAuth. - [`/digest-auth/:qop/:user/:passwd`](http://httpbin.org/digest-auth/auth/user/passwd) Challenges HTTP Digest Auth. diff --git a/httpbin/templates/httpbin.1.html b/httpbin/templates/httpbin.1.html index 5241248..d86cc19 100644 --- a/httpbin/templates/httpbin.1.html +++ b/httpbin/templates/httpbin.1.html @@ -21,7 +21,7 @@
  • /relative-redirect/:n 302 Relative redirects n times.
  • /cookies Returns cookie data.
  • /cookies/set?name=value Sets one or more simple cookies.
  • -
  • /cookies/delete?name Deletes one or more simple cookies.
  • +
  • /cookies/delete?name Deletes one or more simple cookies.
  • /basic-auth/:user/:passwd Challenges HTTPBasic Auth.
  • /hidden-basic-auth/:user/:passwd 404'd BasicAuth.
  • /digest-auth/:qop/:user/:passwd Challenges HTTP Digest Auth.
  • From faf0fea1b294e64cd2bdab0536ae122a7d4ec7ca Mon Sep 17 00:00:00 2001 From: Senko Rasic Date: Fri, 12 Oct 2012 17:01:36 +0200 Subject: [PATCH 3/9] README: add https + show_env example Showcase a useful but undocumented feature of reporting what protocol the request was made over (amongst other info). --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index df6687f..d378e07 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,35 @@ All endpoint responses are JSON-encoded. Content-Length: 135 +### $ curl https://httpbin.org/get?show_env=1 + + { + "headers": { + "Content-Length": "", + "Accept-Language": "en-US,en;q=0.8", + "Accept-Encoding": "gzip,deflate,sdch", + "X-Forwarded-Port": "443", + "X-Forwarded-For": "109.60.101.240", + "X-Heroku-Dynos-In-Use": "1", + "Host": "httpbin.org", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "User-Agent": "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.83 Safari/535.11", + "X-Request-Start": "1350053933441", + "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.3", + "Connection": "keep-alive", + "X-Forwarded-Proto": "https", + "Cookie": "_gauges_unique_day=1; _gauges_unique_month=1; _gauges_unique_year=1; _gauges_unique=1; _gauges_unique_hour=1", + "X-Heroku-Queue-Depth": "0", + "X-Heroku-Queue-Wait-Time": "11", + "Content-Type": "" + }, + "args": { + "show_env": "1" + }, + "origin": "109.60.101.240", + "url": "http://httpbin.org/get?show_env=1" + } + ## AUTHOR A [Kenneth Reitz](http://kennethreitz.com/pages/open-projects.html) From 61bed33f242903e0964cc1e7d860bba128fd7ccb Mon Sep 17 00:00:00 2001 From: Rodrigo Chacon Date: Thu, 10 Jan 2013 03:56:24 -0200 Subject: [PATCH 4/9] Adding CORS support for all requests --- httpbin/core.py | 9 +++++++++ test_httpbin.py | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/httpbin/core.py b/httpbin/core.py index 358b0f1..3c51c00 100644 --- a/httpbin/core.py +++ b/httpbin/core.py @@ -38,6 +38,15 @@ app = Flask(__name__) sentry = Sentry(app) +# ----------- +# Middlewares +# ----------- +@app.after_request +def set_cors_headers(response): + response.headers['Access-Control-Allow-Origin'] = '*' + return response + + # ------ # Routes # ------ diff --git a/test_httpbin.py b/test_httpbin.py index 8d7149c..7504b8d 100755 --- a/test_httpbin.py +++ b/test_httpbin.py @@ -41,6 +41,10 @@ class HttpbinTestCase(unittest.TestCase): response = self.app.post('/post', data={"file": f}) self.assertEquals(response.status_code, 200) + def test_set_cors_headers_after_request(self): + response = self.app.get('/get') + self.assertEquals(response.headers.get('Access-Control-Allow-Origin'), '*') + if __name__ == '__main__': unittest.main() From 11a8c78aa5096b6df75df8580850504835e9dfe4 Mon Sep 17 00:00:00 2001 From: Rodrigo Chacon Date: Mon, 14 Jan 2013 04:38:09 -0200 Subject: [PATCH 5/9] Adding support for preflight request headers Just as stated in http://www.w3.org/TR/cors/#preflight-request, we must return some extra headers when dealing with preflight (HTTP OPTIONS method) requests. --- httpbin/core.py | 5 +++++ test_httpbin.py | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/httpbin/core.py b/httpbin/core.py index 3c51c00..1bad8b3 100644 --- a/httpbin/core.py +++ b/httpbin/core.py @@ -44,6 +44,11 @@ sentry = Sentry(app) @app.after_request def set_cors_headers(response): response.headers['Access-Control-Allow-Origin'] = '*' + if request.method == 'OPTIONS': + response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Credentials'] = 'true' + response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, PATCH, OPTIONS' + response.headers['Access-Control-Max-Age'] = str(60 * 60) # 1 hour cache return response diff --git a/test_httpbin.py b/test_httpbin.py index 7504b8d..0284c85 100755 --- a/test_httpbin.py +++ b/test_httpbin.py @@ -45,6 +45,16 @@ class HttpbinTestCase(unittest.TestCase): response = self.app.get('/get') self.assertEquals(response.headers.get('Access-Control-Allow-Origin'), '*') + def test_set_cors_headers_with_options_verb(self): + response = self.app.open('/get', method='OPTIONS') + self.assertEquals(response.headers.get('Access-Control-Allow-Origin'), '*') + self.assertEquals(response.headers.get('Access-Control-Allow-Credentials'), 'true') + self.assertEquals(response.headers.get('Access-Control-Allow-Methods'), 'GET, POST, PUT, DELETE, PATCH, OPTIONS') + self.assertEquals(response.headers.get('Access-Control-Max-Age'), '3600') + self.assertNotIn('Access-Control-Allow-Headers', response.headers) # FIXME should we add any extra headers? + + + if __name__ == '__main__': unittest.main() From 50ddd78b89c9c6008561323ba350293193239c03 Mon Sep 17 00:00:00 2001 From: Rodrigo Chacon Date: Mon, 14 Jan 2013 04:43:02 -0200 Subject: [PATCH 6/9] PEP8 --- test_httpbin.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test_httpbin.py b/test_httpbin.py index 0284c85..1924179 100755 --- a/test_httpbin.py +++ b/test_httpbin.py @@ -1,9 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import base64 +import unittest import httpbin -import unittest -import base64 def _string_to_base64(string): @@ -54,7 +54,5 @@ class HttpbinTestCase(unittest.TestCase): self.assertNotIn('Access-Control-Allow-Headers', response.headers) # FIXME should we add any extra headers? - - if __name__ == '__main__': unittest.main() From faf559dcf5e130d1d351b75b14ddda61c9fdfaa4 Mon Sep 17 00:00:00 2001 From: Rodrigo Chacon Date: Thu, 31 Jan 2013 02:39:43 -0200 Subject: [PATCH 7/9] Removing redundancy --- httpbin/core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/httpbin/core.py b/httpbin/core.py index 1bad8b3..bdd6f99 100644 --- a/httpbin/core.py +++ b/httpbin/core.py @@ -45,7 +45,6 @@ sentry = Sentry(app) def set_cors_headers(response): response.headers['Access-Control-Allow-Origin'] = '*' if request.method == 'OPTIONS': - response.headers['Access-Control-Allow-Origin'] = '*' response.headers['Access-Control-Allow-Credentials'] = 'true' response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, PATCH, OPTIONS' response.headers['Access-Control-Max-Age'] = str(60 * 60) # 1 hour cache From 3f45aba2afdbd917b404b1a6173a6b2a97bd946a Mon Sep 17 00:00:00 2001 From: Rodrigo Chacon Date: Thu, 31 Jan 2013 02:40:08 -0200 Subject: [PATCH 8/9] Hardcoding cache --- httpbin/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpbin/core.py b/httpbin/core.py index bdd6f99..4bbad50 100644 --- a/httpbin/core.py +++ b/httpbin/core.py @@ -47,7 +47,7 @@ def set_cors_headers(response): if request.method == 'OPTIONS': response.headers['Access-Control-Allow-Credentials'] = 'true' response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, PATCH, OPTIONS' - response.headers['Access-Control-Max-Age'] = str(60 * 60) # 1 hour cache + response.headers['Access-Control-Max-Age'] = '3600' # 1 hour cache return response From e3cd47d0e297a7a5a73cd4921eec865920d2cccd Mon Sep 17 00:00:00 2001 From: Rodrigo Chacon Date: Thu, 31 Jan 2013 02:43:42 -0200 Subject: [PATCH 9/9] Use header Origin in response headers when available Since the RFC [1] doesn't allow wildcards for credentialed requests, add the requested Origin into the response headers. [1] https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS#Requests_with_credentials --- httpbin/core.py | 3 ++- test_httpbin.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/httpbin/core.py b/httpbin/core.py index 4bbad50..14a2df2 100644 --- a/httpbin/core.py +++ b/httpbin/core.py @@ -43,7 +43,8 @@ sentry = Sentry(app) # ----------- @app.after_request def set_cors_headers(response): - response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Origin'] = request.headers.get('Origin', '*') + if request.method == 'OPTIONS': response.headers['Access-Control-Allow-Credentials'] = 'true' response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, PATCH, OPTIONS' diff --git a/test_httpbin.py b/test_httpbin.py index 1924179..4287270 100755 --- a/test_httpbin.py +++ b/test_httpbin.py @@ -45,6 +45,10 @@ class HttpbinTestCase(unittest.TestCase): response = self.app.get('/get') self.assertEquals(response.headers.get('Access-Control-Allow-Origin'), '*') + def test_set_cors_headers_after_request_with_request_origin(self): + response = self.app.get('/get', headers={'Origin': 'origin'}) + self.assertEquals(response.headers.get('Access-Control-Allow-Origin'), 'origin') + def test_set_cors_headers_with_options_verb(self): response = self.app.open('/get', method='OPTIONS') self.assertEquals(response.headers.get('Access-Control-Allow-Origin'), '*')