How do I control the HTTP Status Code in the protoRPC response?
Let's say I have the following service:
class ApiService(remote.Service):
#remote.method(ApiRequestContextCreate, ApiResponseContextCreate)
def context_create(self, request):
cid = helpers.create_context(request.name)
return ApiResponseContextCreate(cid=cid)
Looks to me that the protoRPC API is lacking semantics: either the request can be fulfilled and returns a 200 or an exception is raised yielding to a 404. Of course of could craft an error response in the RPC method but that seems kludgy.
Update: I have found I can raise ApplicationError too for a 400.
The HTTP status code is not part of the specification for ProtoRPC because ProtoRPC is meant to support protocols besides HTTP. It therefore cannot have specific-to-HTTP return codes. Instead, errors are returns by raising ApplicationError, using the error_name field to specify application specific error conditions.
Related
I am using standard error handling in production - if there are server errors I am getting mails.
However, on certain APIs I want to have a response with HTTP code 500 or 502 as part of the "valid flow". (It's done for educational purposes)
So my view for that looks like this:
from rest_framework.response import Response
from rest_framework import status
def give_me_error(request):
return Response("This is expected error", status=status.HTTP_500_INTERNAL_SERVER_ERROR)
And what I want is not to get email of this particular response (because I wanted it to be 500)
I've also tried this way:
from django.http import JsonResponse
def give_me_error(request):
return JsonResponse({'error': 'expected error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
But it also generates mail.
Is there a way to have a view returning error 500 that will not trigger e-mail send? (without disabling the e-mail functionality system-wide)
Per Django documentation: https://docs.djangoproject.com/en/4.0/howto/error-reporting/
When DEBUG is False, Django will email the users listed in the ADMINS setting whenever your code raises an unhandled exception and results in an internal server error (strictly speaking, for any response with an HTTP status code of 500 or greater)
In the python standard environment quickstart, the endpoints method test_api_key returns a 503 Service Unavailable. The error occurs in the API Explorer when run with dev_appser.py and when the API is deployed. The code for it is:
import endpoints
from protorpc import message_types
from protorpc import messages
from protorpc import remote
class TestResponse(messages.Message):
content = messages.StringField(1)
#endpoints.api(name='practice', version='v1', description='My Practice API')
class PracticeApi(remote.Service):
#endpoints.method(
message_types.VoidMessage,
TestResponse,
path='test/getApiKey',
http_method='GET',
name='test_api_key')
def test_api_key(self, request):
return TestResponse(content=request.get_unrecognized_field_info('key'))
api = endpoints.api_server([PracticeApi])
I don't have a good understanding of .get_unrecognized_field_info('key') so I am not sure what the issue is? Thanks.
Firstly, I recommend reading Google Protocol RPC Library Overview, since it's Google Cloud Endpoints uses it extensively.
#endpoints.method allows you to configure a specific method in your API. Configuration options are documented in Google Cloud Platform documentation Creating an API with Cloud Endpoints Frameworks for App Engine, in the section, Defining an API method (#endpoints.method).
If you're restricting access to the test/getApiKey/test_api_key method, then you must configure the method with the api_key_required=True option. Restricting API Access with API Keys (Frameworks) discusses that further, but your method annotation should be:
#endpoints.method(
message_types.VoidMessage,
TestResponse,
path='test/getApiKey',
http_method='GET',
name='test_api_key',
api_key_required=True
)
Notice your method accepts a request parameter representing the HTTP request (i.e. client using your API):
def test_api_key(self, request):
However, the request parameter is actually Google Protocol RPC Message (Proto RPC) Message object and as such is very well defined. If additional fields exist in the ProtoRPC request parameter, beyond what is formally defined, they are still stored with the request object but must be retrieved using the following method:
def get_unrecognized_field_info(self, key, value_default=None,
variant_default=None):
"""Get the value and variant of an unknown field in this message.
Args:
key: The name or number of the field to retrieve.
value_default: Value to be returned if the key isn't found.
variant_default: Value to be returned as variant if the key isn't
found.
Returns:
(value, variant), where value and variant are whatever was passed
to set_unrecognized_field.
"""
Message class code on GitHub is quite well documented. .
No arguments will appear in the body of a request because you've configured the method with to be called with HTTP GET:
http_method='GET'
...you're correctly using the value message_types.VoidMessage.
In terms of your error, 503 is just a generic server error, can you provide any information from the StackDriver logs? They will point you to the exact line and error in your code.
There were three things that were creating the 503 error.
Firstly, I needed to make the method or entire Api require an Api Key. In this case I just applied it to the entire Api:
#endpoints.api(name='practice', version='v1', api_key_required=True)
class PracticeApi(remote.Service):
Secondly, after I generated the Api Key in the cloud console I needed to put the Key into the openapi.json file before deploying it.
Lastly, I was still getting a validation error:
ValidationError: Expected type <type 'unicode'> for field content, found (u'My Api Key', Variant(STRING, 9)) (type <type 'tuple'>)
The get_unrecognized_field_info() function returns a tuple of (value, variant). A tuple was not expected by the response so I updated the method to only show value:
def test_api_key(self, request):
return TestResponse(content=request.get_unrecognized_field_info('key')[0])
I'm working on my first Flask app (version 0.10.1), and also my first Python (version 3.5) app. One of its pieces needs to work like this:
Submit a form
Run a Celery task (which makes some third-party API calls)
When the Celery task's API calls complete, send a JSON post to another URL in the app
Get that JSON data and update a database record with it
Here's the relevant part of the Celery task:
if not response['errors']: # response comes from the Salesforce API call
# do something to notify that the task was finished successfully
message = {'flask_id' : flask_id, 'sf_id' : response['id']}
message = json.dumps(message)
print('call endpoint now and update it')
res = requests.post('http://0.0.0.0:5000/transaction_result/', json=message)
And here's the endpoint it calls:
#app.route('/transaction_result/', methods=['POST'])
def transaction_result():
result = jsonify(request.get_json(force=True))
print(result.flask_id)
return result.flask_id
So far I'm just trying to get the data and print the ID, and I'll worry about the database after that.
The error I get though is this: requests.exceptions.ConnectionError: None: Max retries exceeded with url: /transaction_result/ (Caused by None)
My reading indicates that my data might not be coming over as JSON, hence the Force=True on the result, but even this doesn't seem to work. I've also tried doing the same request in CocoaRestClient, with a Content-Type header of application/json, and I get the same result.
Because both of these attempts break, I can't tell if my issue is in the request or in the attempt to parse the response.
First of all request.get_json(force=True) returns an object (or None if silent=True). jsonify converts objects to JSON strings. You're trying to access str_val.flask_id. It's impossible. However, even after removing redundant jsonify call, you'll have to change result.flask_id to result['flask_id'].
So, eventually the code should look like this:
#app.route('/transaction_result/', methods=['POST'])
def transaction_result():
result = request.get_json()
return result['flask_id']
And you are absolutely right when you're using REST client to test the route. It crucially simplifies testing process by reducing involved parts. One well-known problem during sending requests from a flask app to the same app is running this app under development server with only one thread. In such case a request will always be blocked by an internal request because the current thread is serving the outermost request and cannot handle the internal one. However, since you are sending a request from the Celery task, it's not likely your scenario.
UPD: Finally, the last one reason was an IP address 0.0.0.0. Changing it to the real one solved the problem.
I'm going through the RESTful web services chapter of the Flask web development book by Miguel Grinberg and he mentions that errors can be generated by Flask on its own or explicitly by the web service.
For errors generated by Flask, he uses a error handler like the following:
#main.app_errorhandler(404)
def page_not_found(e):
if request.accept mimetypes.accept_json and \
not request.accept_mimetypes.accept_html:
response = jsonify({'error': 'not found'})
response.status_code = 404
return response
return render_template('404.html'), 404
While errors generated by the web service, have no error handler:
def forbidden(message):
response = jsonify({'error': 'forbidden', 'message': message})
response.status_code = 403
return response
I don't really understand the difference between a flask generated error vs a web service generated error.
The first is an example of how to make a custom handler for an error that Flask will raise. For example, it will raise a 404 error with a default "not found" message if it doesn't recognize the path. The custom handler allows you to still return the 404 error, but with your own, nicer looking response instead.
The second is an example of how to change the code for your response, without handling a previously raised error. In this example, you would probably return forbidden() from another view if the user doesn't have permission. The response would have a 403 code, which your frontend would know how to handle.
I'm trying to use Twilio with Google App Engine. I'm currently trying to validate requests coming in from Twilio with SMS messages. I have a custom handler that has the 2 methods below on it.
from twilio.util import RequestValidator
class TwilioRequestHandler(BaseRequestHandler):
def twilio_request_validator(self):
return RequestValidator(AUTH_TOKEN)
def validate_request(self):
if not 'X-Twilio-Signature' in self.request.headers:
logging.error("X-Twilio-Signature was not in the request headers")
return False
return self.twilio_request_validator().validate(self.request.url, self.request.POST, self.request.headers['X-Twilio-Signature'])
When a request comes in on one of my TwiML endpoints, I call self.validate_request() from my request handler. This always seems to return false. As you can see from my code above, this should be the equivalent of calling Twilio's RequestValidator(AuthToken).validate(self.request.url, self.request.POST, self.request.headers['X-Twilio-Signature'])
I figured that it's possible that some of the request arguments that I received aren't supposed to be included when computing the signature, so I even went so far as taking the arguments for one request, creating a simple script checked all possible combinations, and compared it to the signature for that request. None of them were successful, so I have to be curious what I'm doing wrong, or if this is possibly something on the Twilio side.
Have you checked the protocol on the request URL against the Twilio endpoint?
Heroku apparently proxies HTTPS traffic to HTTP if Flask is configured for HTTP only. Flask's request.url will still begin with http:// even though Twilio is pointing at a URL that begins with https://. This will throw off hash.