Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'after_request' runs but headers don't get set #1125

Open
theSage21 opened this issue Jan 15, 2019 · 15 comments
Open

'after_request' runs but headers don't get set #1125

theSage21 opened this issue Jan 15, 2019 · 15 comments
Labels
Bug This issue is an actual confirmed bug that needs fixing Change Neigher a bug nor a freature, but something that needs to be addressed.

Comments

@theSage21
Copy link
Contributor

import bottle


app = bottle.Bottle()

@app.hook('after_request')
def fn():
    print(dict(bottle.response.headers))
    bottle.response.headers['abc'] = 'yes'
    print(dict(bottle.response.headers))


@app.get('/')
def asdf():
    return ''


app.run()

This code runs properly. I'm able to get the headers when I make a request from another program. However if I change the route to

@app.get('/')
def asdf():
    bottle.abort(424)

The hook gets run since I can see it in the server process log

Bottle v0.12.13 server starting up (using WSGIRefServer())...
Listening on http://127.0.0.1:8080/
Hit Ctrl-C to quit.

{}
{'abc': 'yes'}
127.0.0.1 - - [15/Jan/2019 21:23:03] "GET / HTTP/1.1" 424 726

but the header is not received in the program which made the call. That program is also quiet simple. curl http://localhost:8080 -D-

I saw #978 and was facing the same problem. Some of the UI frameworks using our APIs break if the error response does not have CORS headers.

Am I doing something wrong?

@theSage21
Copy link
Contributor Author

theSage21 commented Jan 16, 2019

Current master does not have this problem, although if you check out 0.12.13 the behavior is there. Should I close the issue? Or maybe a PR with a test to catch this case?

@nicoabie
Copy link

This issue is still valid in latest stable release which is 0.12.16 at the moment of writting this comment.

Particularly fails when working with static_files:

@hook('after_request')
def enable_cors_after_request_hook():
   print("add_cors_headers")
    response.headers['Access-Control-Allow-Origin'] = '*'
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS'
    response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'

@get('/benchmark/<feed>/<strategy>/<name>')
def bench_get(feed, strategy, name):
    return static_file(name + '.json', root="../benchs/{}/{}".format(feed, strategy))

It calls the function but header are not set.

@defnull
Copy link
Member

defnull commented Jan 31, 2019

It's solved in 0.13 but this gets reported quite often for 0.12, so I'll look into a backport or pull requests fixing this specific issue for 0.12. Let's keep this issue open for now.

@defnull defnull added Bug This issue is an actual confirmed bug that needs fixing Change Neigher a bug nor a freature, but something that needs to be addressed. labels Jan 31, 2019
@yoch
Copy link

yoch commented Sep 4, 2019

@defnull This is definitively very annoying, especially for CORS gestion (for example, all errors are wrongly reported as CORS problems). Can you provide a fix for 0.12 version please ?

defnull added a commit that referenced this issue Dec 8, 2019
This is a backport of the 0.13 _handle() logic and only changes
undefined/undocumented behavior (for the better).
defnull added a commit that referenced this issue Dec 8, 2019
defnull added a commit that referenced this issue Dec 8, 2019
defnull added a commit that referenced this issue Dec 8, 2019
@defnull
Copy link
Member

defnull commented Dec 8, 2019

Can someone please verify that the current release-12 branch fixes this issue? gh-actions are not executed for 0.12 and travis-ci is broken for old python versions.

@yoch
Copy link

yoch commented Dec 9, 2019

I've tested the defnull-12-1125 branch, and I can confirm that the issue is fixed.
My python version is 3.6

I also ran the tests with python3 test/testall.py, and here is the output (1 failure):

/home/yoch/bottle/test/test_environ.py:302: DeprecationWarning: Please use assertEqual instead.
  self.assertEquals(tob(expect), BaseRequest(e).body.read())
/usr/lib/python3.6/ctypes/__init__.py:60: ResourceWarning: unclosed file <_io.FileIO name=3 mode='rb+' closefd=True>
  buftype = c_char * init
/home/yoch/bottle/test/test_environ.py:581: DeprecationWarning: Please use assertEqual instead.
  self.assertEquals('test/some', rs.headers.get('Content-Type'))
/home/yoch/bottle/bottle.py:557: DeprecationWarning: inspect.getargspec() is deprecated since Python 3.0, use inspect.signature() or inspect.getfullargspec()
  return getargspec(self.get_undecorated_callback())[0]
/home/yoch/bottle/bottle.py:386: RuntimeWarning: Route <GET /object/<id:float>> overwrites a previously defined route
  warnings.warn(msg % (method, rule), RuntimeWarning)
/home/yoch/bottle/bottle.py:386: RuntimeWarning: Route <GET /func2(:param#(foo|bar)#)> overwrites a previously defined route
  warnings.warn(msg % (method, rule), RuntimeWarning)
/home/yoch/bottle/test/test_resources.py:76: ResourceWarning: unclosed file <_io.TextIOWrapper name='/home/yoch/bottle/test/test_resources.py' mode='r' encoding='UTF-8'>
  self.assertEqual(fp.read(), open(__file__).read())
/usr/lib/python3.6/unittest/case.py:605: ResourceWarning: unclosed file <_io.TextIOWrapper name='/home/yoch/bottle/test/test_resources.py' mode='r' encoding='UTF-8'>
  testMethod()
/home/yoch/bottle/test/test_sendfile.py:85: ResourceWarning: unclosed file <_io.BufferedReader name='/home/yoch/bottle/test/test_sendfile.py'>
  f = static_file(os.path.basename(__file__), root='./')
/home/yoch/bottle/test/test_sendfile.py:86: ResourceWarning: unclosed file <_io.BufferedReader name='/home/yoch/bottle/test/test_sendfile.py'>
  self.assertEqual(open(__file__,'rb').read(), f.body.read())
/usr/lib/python3.6/unittest/case.py:605: ResourceWarning: unclosed file <_io.BufferedReader name='/home/yoch/bottle/test/test_sendfile.py'>
  testMethod()
/home/yoch/bottle/test/test_sendfile.py:77: ResourceWarning: unclosed file <_io.BufferedReader name='/home/yoch/bottle/test/test_sendfile.py'>
  self.assertEqual(open(__file__,'rb').read(), static_file(os.path.basename(__file__), root='./').body.read())
/home/yoch/bottle/test/test_sendfile.py:62: ResourceWarning: unclosed file <_io.BufferedReader name='/home/yoch/bottle/test/test_sendfile.py'>
  f = static_file(os.path.basename(__file__), root='./', mimetype='some/type')
/home/yoch/bottle/test/test_sendfile.py:64: ResourceWarning: unclosed file <_io.BufferedReader name='/home/yoch/bottle/test/test_sendfile.py'>
  f = static_file(os.path.basename(__file__), root='./', mimetype='text/foo')
/home/yoch/bottle/test/test_sendfile.py:66: ResourceWarning: unclosed file <_io.BufferedReader name='/home/yoch/bottle/test/test_sendfile.py'>
  f = static_file(os.path.basename(__file__), root='./', mimetype='text/foo', charset='latin1')
/home/yoch/bottle/test/test_sendfile.py:93: ResourceWarning: unclosed file <_io.BufferedReader name='/home/yoch/bottle/test/test_sendfile.py'>
  self.assertEqual(c.read(16), tob('').join(f.body))
/home/yoch/bottle/test/test_sendfile.py:94: ResourceWarning: unclosed file <_io.BufferedReader name='test_sendfile.py'>
  self.assertEqual('bytes 10-25/%d' % len(open(basename, 'rb').read()),
/usr/lib/python3.6/unittest/case.py:605: ResourceWarning: unclosed file <_io.BufferedReader name='test_sendfile.py'>
  testMethod()
/home/yoch/bottle/test/test_sendfile.py:43: ResourceWarning: unclosed file <_io.BufferedReader name='/home/yoch/bottle/test/test_sendfile.py'>
  self.assertEqual(open(__file__,'rb').read(), out.body.read())
/home/yoch/bottle/bottle.py:2657: DeprecationWarning: inspect.getargspec() is deprecated since Python 3.0, use inspect.signature() or inspect.getfullargspec()
  spec = getargspec(func)
/home/yoch/bottle/bottle.py:3593: DeprecationWarning: The include and rebase keywords are functions now.
  line, comment = self.fix_backward_compatibility(line, comment)
/home/yoch/bottle/test/test_wsgi.py:280: DeprecationWarning: Please use assertEqual instead.
  self.assertEquals('/test/6', bottle.url('foo', x=6))
/home/yoch/bottle/bottle.py:3593: DeprecationWarning: PEP263 encoding strings in templates are deprecated.
  line, comment = self.fix_backward_compatibility(line, comment)
/home/yoch/bottle/bottle.py:195: DeprecationWarning: Template encodings other than utf8 are no longer supported.
  value = obj.__dict__[self.func.__name__] = self.func(obj)
/home/yoch/bottle/bottle.py:3387: DeprecationWarning: Escape code lines with a backslash.
  code = parser.translate()
/home/yoch/bottle/bottle.py:3239: DeprecationWarning: The template lookup path list should not be empty.
  self.filename = self.search(self.name, self.lookup)
/home/yoch/bottle/test/test_stpl.py:55: DeprecationWarning: Please use assertEqual instead.
  self.assertEquals('"&lt;&#039;&#13;&#10;&#9;&quot;\\&gt;"', html_quote('<\'\r\n\t"\\>'));
/home/yoch/bottle/test/test_server.py:47: ResourceWarning: unclosed <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 39024)>
  if ping('127.0.0.1', port): return
WARNING: Skipping 'meinheld' test (ImportError).
/usr/lib/python3.6/unittest/suite.py:84: ResourceWarning: unclosed file <_io.BufferedReader name=3>
  return self.run(*args, **kwds)
/usr/lib/python3.6/unittest/suite.py:84: ResourceWarning: unclosed file <_io.BufferedReader name=5>
  return self.run(*args, **kwds)
/home/yoch/bottle/test/test_server.py:47: ResourceWarning: unclosed <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 39026)>
  if ping('127.0.0.1', port): return
WARNING: Skipping 'bjoern' test (ImportError).
/home/yoch/bottle/test/test_server.py:47: ResourceWarning: unclosed <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 39028)>
  if ping('127.0.0.1', port): return
WARNING: Skipping 'cherrypy' test (ImportError).
/home/yoch/bottle/test/test_server.py:47: ResourceWarning: unclosed <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 39038)>
  if ping('127.0.0.1', port): return
WARNING: Skipping 'diesel' test (ImportError).
/home/yoch/bottle/test/test_server.py:47: ResourceWarning: unclosed <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 39040)>
  if ping('127.0.0.1', port): return
WARNING: Skipping 'eventlet' test (ImportError).
/home/yoch/bottle/test/test_server.py:47: ResourceWarning: unclosed <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 39042)>
  if ping('127.0.0.1', port): return
WARNING: Skipping 'fapws3' test (ImportError).
/home/yoch/bottle/test/test_server.py:47: ResourceWarning: unclosed <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 39044)>
  if ping('127.0.0.1', port): return
WARNING: Skipping 'gevent' test (ImportError).
/home/yoch/bottle/test/test_server.py:47: ResourceWarning: unclosed <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 39046)>
  if ping('127.0.0.1', port): return
WARNING: Skipping 'gunicorn' test (ImportError).
/home/yoch/bottle/test/test_server.py:47: ResourceWarning: unclosed <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 39048)>
  if ping('127.0.0.1', port): return
WARNING: Skipping 'paste' test (ImportError).
/home/yoch/bottle/test/test_server.py:47: ResourceWarning: unclosed <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 39050)>
  if ping('127.0.0.1', port): return
WARNING: Skipping 'rocket' test (ImportError).
/home/yoch/bottle/test/test_server.py:47: ResourceWarning: unclosed <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 39064)>
  if ping('127.0.0.1', port): return
/home/yoch/bottle/test/test_server.py:47: ResourceWarning: unclosed <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 39078)>
  if ping('127.0.0.1', port): return
WARNING: Skipping 'twisted' test (ImportError).
/home/yoch/bottle/test/test_config.py:39: DeprecationWarning: Please use assertEqual instead.
  self.assertEquals(c['a.b.foo'], 5)
/home/yoch/bottle/test/test_config.py:40: DeprecationWarning: Please use assertEqual instead.
  self.assertEquals(c['a.b.bar'], 6)
/home/yoch/bottle/test/test_config.py:41: DeprecationWarning: Please use assertEqual instead.
  self.assertEquals(c['a.baz'], 7)
/home/yoch/bottle/test/test_config.py:32: DeprecationWarning: Please use assertEqual instead.
  self.assertEquals(c['int'], 6)
/home/yoch/bottle/test/test_configdict.py:30: DeprecationWarning: Attribute assignment is deprecated.
  c.test = 5
/home/yoch/bottle/test/test_configdict.py:31: DeprecationWarning: Attribute access is deprecated.
  self.assertEqual(5, c.test)
/home/yoch/bottle/test/test_configdict.py:34: DeprecationWarning: Attribute access is deprecated.
  self.assertEqual(6, c.test)
/home/yoch/bottle/test/test_configdict.py:38: DeprecationWarning: Attribute access is deprecated.
  self.assertEqual(None, c.test)
/home/yoch/bottle/test/test_configdict.py:61: DeprecationWarning: Calling ConfDict is deprecated. Use the update() method.
  self.assertEqual(c, c(a=1))
/home/yoch/bottle/test/test_configdict.py:63: DeprecationWarning: Attribute access is deprecated.
  self.assertEqual(1, c.a)
/home/yoch/bottle/test/test_configdict.py:12: DeprecationWarning: Constructor does no longer accept parameters.
  d, m = dict(a=5), ConfigDict(a=5)
/home/yoch/bottle/test/test_configdict.py:70: DeprecationWarning: Accessing namespaces as dicts is discouraged. Only use flat item access: cfg["names"]["pace"]["key"] -> cfg["name.space.key"]
  self.assertEqual('c', c['a']['b'])
/usr/lib/python3.6/_collections_abc.py:744: DeprecationWarning: Accessing namespaces as dicts is discouraged. Only use flat item access: cfg["names"]["pace"]["key"] -> cfg["name.space.key"]
  yield (key, self._mapping[key])
/home/yoch/bottle/test/test_configdict.py:43: DeprecationWarning: Attribute access is deprecated.
  self.assertEqual(ConfigDict.Namespace, c.Name.Space.__class__)
/usr/lib/python3.6/_collections_abc.py:660: DeprecationWarning: Accessing namespaces as dicts is discouraged. Only use flat item access: cfg["names"]["pace"]["key"] -> cfg["name.space.key"]
  return self[key]
/home/yoch/bottle/test/test_configdict.py:44: DeprecationWarning: Attribute access is deprecated.
  c.Name.Space.value = 5
/home/yoch/bottle/test/test_configdict.py:44: DeprecationWarning: Attribute assignment is deprecated.
  c.Name.Space.value = 5
/home/yoch/bottle/test/test_configdict.py:45: DeprecationWarning: Attribute access is deprecated.
  self.assertEqual(5, c.Name.Space.value)
/home/yoch/bottle/test/test_configdict.py:46: DeprecationWarning: Attribute access is deprecated.
  self.assertTrue('value' in c.Name.Space)
/home/yoch/bottle/test/test_configdict.py:47: DeprecationWarning: Attribute access is deprecated.
  self.assertTrue('Space' in c.Name)
/home/yoch/bottle/test/test_configdict.py:51: DeprecationWarning: Attribute assignment is deprecated.
  self.assertRaises(AttributeError, lambda: setattr(c, 'Name', 5))
/home/yoch/bottle/test/test_configdict.py:53: DeprecationWarning: Attribute assignment is deprecated.
  self.assertRaises(AttributeError, lambda: setattr(c, 'keys', 5))
/home/yoch/bottle/test/test_configdict.py:56: DeprecationWarning: Attribute access is deprecated.
  self.assertEqual(5, c.Name)
/home/yoch/bottle/bottle.py:3354: DeprecationWarning: The template lookup path list should not be empty.
  fname = self.search(name, self.lookup)
/home/yoch/bottle/bottle.py:3354: DeprecationWarning: Absolute template path names are deprecated.
  fname = self.search(name, self.lookup)
/home/yoch/bottle/bottle.py:533: DeprecationWarning: Switch to Plugin API v2 and access the Route object directly.
  context = self if api > 1 else self._context
/home/yoch/bottle/test/test_plugins.py:198: DeprecationWarning: Please use assertEqual instead.
  self.assertEquals(getattr(plugin, 'app', None), self.app)
/usr/lib/python3.6/unittest/case.py:605: ResourceWarning: unclosed file <_io.BufferedReader name='/home/yoch/bottle/test/test_fileupload.py'>
  testMethod()
/home/yoch/bottle/test/test_fileupload.py:67: ResourceWarning: unclosed file <_io.BufferedReader name='/tmp/tmpjpneaez0/test_fileupload.py'>
  self.assertEqual(fu.file.read(), open(filepath, 'rb').read())
/usr/lib/python3.6/unittest/case.py:605: ResourceWarning: unclosed file <_io.BufferedRandom name=4>
  testMethod()
======================================================================
FAIL: test_delete_cookie (test_environ.TestResponse)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/yoch/bottle/test/test_environ.py", line 627, in test_delete_cookie
    self.assertTrue('name=;' in cookies[0])
AssertionError: False is not true

----------------------------------------------------------------------
Ran 334 tests in 2.990s

FAILED (failures=1)

@martinlehoux
Copy link

martinlehoux commented Jan 28, 2020

I think I find an issue. I am using Bottle auth_basic decorator, and it seems that when raising a 401 error, headers are not set.

Here is a route without authorization, headers are set.

curl http://localhost:8000/ -v        
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.64.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: gunicorn/20.0.4
< Date: Tue, 28 Jan 2020 18:06:17 GMT
< Connection: close
< Content-Type: application/json
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS
< Access-Control-Allow-Headers: Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token
< Content-Length: 26
< 
* Closing connection 0
{"message": "Hello world"}%

Here on a protected route :

curl http://localhost:8000/users/me -v
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /users/me HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.64.1
> Accept: */*
> 
< HTTP/1.1 401 Unauthorized
< Server: gunicorn/20.0.4
< Date: Tue, 28 Jan 2020 18:08:33 GMT
< Connection: close
< Www-Authenticate: Basic realm="private"
< Content-Length: 723
< Content-Type: text/html; charset=UTF-8
< 

    <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
    <html>
        <head>
            <title>Error: 401 Unauthorized</title>
            <style type="text/css">
              html {background-color: #eee; font-family: sans;}
              body {background-color: #fff; border: 1px solid #ddd;
                    padding: 15px; margin: 15px;}
              pre {background-color: #eee; border: 1px solid #ddd; padding: 5px;}
            </style>
        </head>
        <body>
            <h1>Error: 401 Unauthorized</h1>
            <p>Sorry, the requested URL <tt>&#039;http://localhost:8000/users/me&#039;</tt>
               caused an error:</p>
            <pre>Access denied</pre>
        </body>
    </html>
* Closing connection 0

It's even weirder because the after_request hook is called in both cases.

@application.hook('after_request')
def enable_cors():
    response.headers['Access-Control-Allow-Origin'] = '*'
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS'
    response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'
    print(response.headerlist)

EDIT

It does work in 0.13

@willsthompson
Copy link

It's solved in 0.13 but this gets reported quite often for 0.12, so I'll look into a backport or pull requests fixing this specific issue for 0.12. Let's keep this issue open for now.

Where is 0.13 available? I don't see any releases or tags or even a dev branch related to 0.13 in the repo

@oz123
Copy link
Contributor

oz123 commented Jun 7, 2020

That's the current master branch.

@willsthompson
Copy link

That's the current master branch.

Thank you! So master branch in the repo corresponds to the Bottle dev (development) documentation?

@oz123
Copy link
Contributor

oz123 commented Jun 7, 2020

Yes. There is no "dev" branch.

@martinkirch
Copy link

Hello,
The test proposed at the beginning of this issue fails on Bottle 0.12.19.
Hopefully 0.13 will be out soon !

@oz123
Copy link
Contributor

oz123 commented Mar 30, 2021

@martinkirch even if 0.13 isn't release soon, you can definitely use the master branch in production.
Using in production for a very, very long time now.

@defnull
Copy link
Member

defnull commented Jun 12, 2022

Cookie test is fixed now in release-0.12 branch (will be part of next release, whenever that happens).

The actual issue is still open for 0.12 I guess.

@CrimsonGlory
Copy link

Testing with 0.13.dev0 aka master branch, with python 2.7 (yes, we are migrating, but this is quite big) seems that headers set before the request (in a middleware via decorators, using response.add_header function) get deleted when using static_file.
So when after_request runs, those headers are not present. I guess this is not the desired behavior, right?
I could open another issue "'decorator' runs but headers don't get set", but it is the same as this issue except with the headers being set before.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug This issue is an actual confirmed bug that needs fixing Change Neigher a bug nor a freature, but something that needs to be addressed.
Projects
None yet
Development

No branches or pull requests

9 participants