I am trying to raise a 401/403 status for when a user attempts to access something they don't have privileges to. I used the Webapp2 Exceptions example which generates the proper error codes for 404/500 "natural" events. Such as going to http://localhost:8080/nourl generates the proper 404 and messing up code generates a 500. But when I use the method such as below to set the code using self.error(XXX) I see the code in the console but it does not show up in the browser. EG If you leave self.error() empty it generates the proper 500 code. If you use self.error(500) the console outputs:
INFO 2012-05-09 18:25:29,549 dev_appserver.py:2891] "GET / HTTP/1.1" 500 -
But the browser is completely blank. Below is an example app that exhibits this behaviour. Simply change the self.error() line to the desired code and run. The expected result would be that it generates the proper response to the browser based on the code supplied, not just when a "natural" event occurs such as 404.
main.py
import webapp2
import wsgiref.handlers
import logging
from google.appengine.api import users
class HomeHandler(webapp2.RequestHandler):
def get(self):
user = users.get_current_user()
if user:
self.response.out.write("Hi :1", user.nickname)
else:
self.error(401)
app = webapp2.WSGIApplication([
(r'/', HomeHandler),
], debug=True)
def handle_401(request, response, exception):
logging.exception(exception)
response.write("401 Error")
response.set_status(401)
def handle_403(request, response, exception):
logging.exception(exception)
response.write("403 Error")
response.set_status(403)
def handle_404(request, response, exception):
logging.exception(exception)
response.write("404 Error")
response.set_status(404)
def handle_500(request, response, exception):
logging.exception(exception)
response.write("500 Error")
response.set_status(500)
app.error_handlers[401] = handle_401
app.error_handlers[403] = handle_403
app.error_handlers[404] = handle_404
app.error_handlers[500] = handle_500
# Run the application
def main():
app.run()
app.yaml
application: 401test
version: 1
runtime: python27
api_version: 1
threadsafe: yes
libraries:
- name: webapp2
version: latest
handlers:
- url: /.*
script: main.app
you are using py27 with threadsafe environment.
in app.yaml you set script: main.app so the code after app is defined is not executed.
didn't test it but this should work:
# create a bare app
bare_app = webapp2.WSGIApplication(debug=True)
#define the error handlers
def handle_401(request, response, exception):
logging.exception(exception)
response.write("401 Error")
response.set_status(401)
def handle_403(request, response, exception):
logging.exception(exception)
response.write("403 Error")
response.set_status(403)
def handle_404(request, response, exception):
logging.exception(exception)
response.write("404 Error")
response.set_status(404)
def handle_500(request, response, exception):
logging.exception(exception)
response.write("500 Error")
response.set_status(500)
# add the error handlers
bare_app.error_handlers[401] = handle_401
bare_app.error_handlers[403] = handle_403
bare_app.error_handlers[404] = handle_404
bare_app.error_handlers[500] = handle_500
bare_app.router.add((r'/', HomeHandler))
app = bare_app
edit:
use self.abort() instead of self.error()
http://webapp-improved.appspot.com/guide/exceptions.html#abort
you can see the difference in the source code:
http://code.google.com/p/webapp-improved/source/browse/webapp2.py#574
while self.error() sets the status code but clears the response, self.abort() executes the function that takes care of the error handling.
Related
This is my first ever question.
I'm using react/vite and Rails 7 to build a firehouse management web app. I originally set up rails as an api with --api. Right now, I can log in but when the user clicks home, or any other link on the page, I loose the authorization(or thats what I'm thinking). I'm using the Bcrypt gem. The console.log(user) on my other pages is returning null, but on the inital login it returns the user object. Now, I have another issue with the logging in all together.
I'm getting a 422 'Unprocessable entity' where my request.base_url doesnt match the localhost:3000. I'm assuming thats because vite is running on 5173?
Here is the error
{status: 422, error: 'Unprocessable Entity', exception: '#<ActionController::InvalidAuthenticityToken: HTTP…t match request.base_url (http://localhost:3000)>', traces: {…}}
error
:
"Unprocessable Entity"
exception
:
"#<ActionController::InvalidAuthenticityToken: HTTP Origin header (http://127.0.0.1:5173) didn't match request.base_url (http://localhost:3000)>"
status
:
422
puma.rb
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port ENV.fetch("PORT") { 3000 }
# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "development" }
# Specifies the `pidfile` that Puma will use.
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
I tried to convert rails to the full framework because I thought it was something with the session and cookies. I added a cookie serializer and a session_store.
application.rb
class Application < Rails::Application
# Adding cookies and session middleware
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore
config.api_only = false
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 7.0
# This will allow any origin to make requests to any resource on your server, using any HTTP method.
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*',
headers: :any,
methods: %i[get post put patch delete options head]
end
end
end
end
cookie_serializer.rb
Rails.application.config.action_dispatch.cookies_serializer = :hybrid
session_store.rb
if Rails.env === 'production'
Rails.application.config.session_store :cookie_store, key: '_fire-sphere', domain: '_fire-sphere-json-api'
else
Rails.application.config.session_store :cookie_store, key: '_fire-sphere'
end
Here is my application_controller.rb
class ApplicationController < ActionController::Base
include ActionController::Cookies
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity
def authorized
return render json: {error: "Not Authorized"}, status: :unauthorized unless session.include? :current_user
end
private
def render_unprocessable_entity(invalid)
render json: {errors: invalid.record.errors.full_messages}, status: :unprocessable_entity
end
def render_not_found(error)
# byebug
render json: {error: "#{error.model} Not Found"}, status: :not_found
end
end
show method in users_controller.rb
def show
# using session to find user in question. sessions are in user browser
# if session for user currently happening, set our user to that user and render json
# byebug
current_user = User.find_by(id: session[:current_user])
render json: current_user
end
I think somehow the user isn't getting stored in the session. I was able to check the params on my initial problem and the user was in there but not when I navigated away. I think I've shnaged somthething somewhere and caused a whole other problem now. Thank you for taking a look! I hope it is something simple..
I am trying to redirect from POST to GET. How to achieve this in FastAPI?
What did you try?
I have tried below with HTTP_302_FOUND, HTTP_303_SEE_OTHER as suggested from Issue#863#FastAPI: But Nothing Works!
It always shows INFO: "GET / HTTP/1.1" 405 Method Not Allowed
from fastapi import FastAPI
from starlette.responses import RedirectResponse
import os
from starlette.status import HTTP_302_FOUND,HTTP_303_SEE_OTHER
app = FastAPI()
#app.post("/")
async def login():
# HTTP_302_FOUND,HTTP_303_SEE_OTHER : None is working:(
return RedirectResponse(url="/ressource/1",status_code=HTTP_303_SEE_OTHER)
#app.get("/ressource/{r_id}")
async def get_ressource(r_id:str):
return {"r_id": r_id}
# tes is the filename(tes.py) and app is the FastAPI instance
if __name__ == '__main__':
os.system("uvicorn tes:app --host 0.0.0.0 --port 80")
You can also see this issue here at FastAPI BUGS Issues
I also ran into this and it was quite unexpected. I guess the RedirectResponse carries over the HTTP POST verb rather than becoming an HTTP GET. The issue covering this over on the FastAPI GitHub repo had a good fix:
POST endpoint
import starlette.status as status
#router.post('/account/register')
async def register_post():
# Implementation details ...
return fastapi.responses.RedirectResponse(
'/account',
status_code=status.HTTP_302_FOUND)
Basic redirect GET endpoint
#router.get('/account')
async def account():
# Implementation details ...
The important and non-obvious aspect here is setting status_code=status.HTTP_302_FOUND.
For more info on the 302 status code, check out https://httpstatuses.com/302 Specifically:
Note: For historical reasons, a user agent MAY change the request method from POST to GET for the subsequent request. If this behavior is undesired, the 307 Temporary Redirect status code can be used instead.
In this case, that verb change is exactly what we want.
My appengine app has a cron job that calls a url endpoint whose handler uses mail.send_mail from google.appengine.api. This has been working fine for several months so far.
Today, the email never arrived. I wrote some test code to invoke send_mail, but the email does not get sent. I have adhered to the necesarry requirements like sending from a email address of the form anything#appname.appspotmail.com.
The function is not throwing any exception either. The appengine logs note that the url is invoked, but there is no error or exception.
What might be the problem? Thanks.
Editing to add some code as suggested. Note that to actually test this code one'd need an AppEngine App. In that case you'd need to change myApp etc. in the code below to the actual app name that is used.
Looking forward to any help/insights.
from google.appengine.api import mail
class TestEmailHandler(webapp2.RequestHandler):
def get(self):
mySender = "mySender <mySender#myApp.appspotmail.com>"
myTo = "myToAddress#example.com"
mySubject = "Test Subject"
myBody = "Test Body Text"
myHtml = "<html><body>Test body</body></html>"
try:
mail.send_mail(sender=mySender,
to=myTo,
subject=mySubject,
body=myBody,
html=myHtml)
self.response.headers['Content-Type'] = 'text/plain'
self.response.write("Sent email. Body: " + myBody)
except:
self.response.write("Exception. " + sys.exc_info()[0])
application = webapp2.WSGIApplication([
('/', MainPage),
('/test_email', TestEmailHandler)
], debug=True)
My app.yaml looks like this:
application: myApp
version: 2
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /.*
script: myApp.application
inbound_services:
- mail
I think the problem's now fixed. I associated my credit card by enabling billing and now an email got sent when I tested it. Who would've thought...
Can error_handler be set for a blueprint?
#blueprint.errorhandler(404)
def page_not_found(error):
return 'This page does not exist', 404
edit:
https://github.com/mitsuhiko/flask/blob/18413ed1bf08261acf6d40f8ba65a98ae586bb29/flask/blueprints.py
you can specify an app wide and a blueprint local error_handler
You can use Blueprint.app_errorhandler method like this:
bp = Blueprint('errors', __name__)
#bp.app_errorhandler(404)
def handle_404(err):
return render_template('404.html'), 404
#bp.app_errorhandler(500)
def handle_500(err):
return render_template('500.html'), 500
errorhandler is a method inherited from Flask, not Blueprint.
If you are using Blueprint, the equivalent is app_errorhandler.
The documentation suggests the following approach:
def app_errorhandler(self, code):
"""Like :meth:`Flask.errorhandler` but for a blueprint. This
handler is used for all requests, even if outside of the blueprint.
"""
Therefore, this should work:
from flask import Blueprint, render_template
USER = Blueprint('user', __name__)
#USER.app_errorhandler(404)
def page_not_found(e):
""" Return error 404 """
return render_template('404.html'), 404
On the other hand, while the approach below did not raise any error for me, it didn't work:
from flask import Blueprint, render_template
USER = Blueprint('user', __name__)
#USER.errorhandler(404)
def page_not_found(e):
""" Return error 404 """
return render_template('404.html'), 404
add error handling at application level using the request proxy object:
from flask import request,jsonify
#app.errorhandler(404)
#app.errorhandler(405)
def _handle_api_error(ex):
if request.path.startswith('/api/'):
return jsonify(ex)
else:
return ex
flask Documentation
I too couldn't get the top rated answer to work, but here's a workaround.
You can use a catch-all at the end of your Blueprint, not sure how robust/recommended it is, but it does work. You could also add different error messages for different methods too.
#blueprint.route('/<path:path>')
def page_not_found(path):
return "Custom failure message"
Surprised others didn't mention miguelgrinberg's excellent tutorial.
https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-vii-error-handling
I found the sentry framework for error handling (links below). Seems overly complex. not sure of the threshold where it becomes useful.
https://flask.palletsprojects.com/en/1.1.x/errorhandling/
https://docs.sentry.io/platforms/python/guides/flask/
I combined previous excellent answers with the official docs from Flask, section 'Returning API Errors as JSON', in order to provide a more general approach.
Here is a working PoC that you can copy and paste on your registered blueprint API route handler (e.g. app/api/routes.py):
#blueprint.app_errorhandler(HTTPException)
def handle_exception(e):
"""Return JSON instead of HTML for HTTP errors."""
# start with the correct headers and status code from the error
response = e.get_response()
# replace the body with JSON
response.data = json.dumps({
"code": e.code,
"name": e.name,
"description": e.description,
})
response.content_type = "application/json"
return response
Flask doesnt support blueprint level error handlers for 404 and 500 errors. A BluePrint is a leaky abstraction. Its better to use a new WSGI App for this, if you need separate error handlers, this makes more sense.
Also i would recommend not to use flask, it uses globals all over the places, which makes your code difficult to manage if it grows bigger.
I'm using the webapp2 framework in Google App Engine (Python). In webapp2 exception handling: exceptions in the WSGI app it's described how to handle 404 errors in a function:
import logging
import webapp2
def handle_404(request, response, exception):
logging.exception(exception)
response.write('Oops! I could swear this page was here!')
response.set_status(404)
def handle_500(request, response, exception):
logging.exception(exception)
response.write('A server error occurred!')
response.set_status(500)
app = webapp2.WSGIApplication([
webapp2.Route('/', handler='handlers.HomeHandler', name='home')
])
app.error_handlers[404] = handle_404
app.error_handlers[500] = handle_500
How can I handle the 404 error in a webapp2.RequestHandler class, in the .get() method of that class?
Edit:
The reason I want to call a RequestHandler is to access the session (request.session). Otherwise I'm not able to pass the current user to the template of the 404 error page. i.e. on the StackOverflow 404 error page you can see your username. I would like to display the username of the current user on my website's 404 error page as well. Is this possible in a function or does it has to be a RequestHandler?
Correct code based on #proppy's answer:
class Webapp2HandlerAdapter(webapp2.BaseHandlerAdapter):
def __call__(self, request, response, exception):
request.route_args = {}
request.route_args['exception'] = exception
handler = self.handler(request, response)
return handler.get()
class Handle404(MyBaseHandler):
def get(self):
self.render(filename="404.html",
page_title="404",
exception=self.request.route_args['exception']
)
app = webapp2.WSGIApplication(urls, debug=True, config=config)
app.error_handlers[404] = Webapp2HandlerAdapter(Handle404)
The calling convention of error handler and request handler callables are different:
error_handlers takes (request, response, exception)
RequestHandler takes (request, response)
You may use something similar to Webapp2HandlerAdapter to adapt a webapp2.RequestHandler to a callable.
class Webapp2HandlerAdapter(BaseHandlerAdapter):
"""An adapter to dispatch a ``webapp2.RequestHandler``.
The handler is constructed then ``dispatch()`` is called.
"""
def __call__(self, request, response):
handler = self.handler(request, response)
return handler.dispatch()
But you would have to sneak the extra exception argument in request route_args.