Locust: No statistics shown - python

I'm new to Locust, and I am attempting to log statistics for a POST request, and I'm using the following code along with a generic call to locust.
import json
from locust import HttpUser, task, between
import cfg
class BasicUser(HttpUser):
wait_time = between(1, 3)
v1_data = json.load(open("v1_sample_data.json", "r"))
#task
def get_v1_prediction(self):
route = "/" + cfg.lookup("model.v1.route")
response = self.client.post(
route,
json=self.v1_data,
catch_response=True,
name="API Call"
)
print(response.text)
When I start an experiment, the host is called successfully, and response.text has the expected value and is printed to the console repeatedly. However, the statistics aren't logged.
When I use a GET request in place of the POST without passing data, statistics are logged (though it's only failures because the web app only allows POST requests). Any idea what's going on here?

The catch_response=True is the culprit.
From the documentation:
catch_response – (optional) Boolean argument that, if set, can be used to make a request return a context manager to work as argument to a with statement. This will allow the request to be marked as a fail based on the content of the response, even if the response code is ok (2xx). The opposite also works, one can use catch_response to catch a request and then mark it as successful even if the response code was not (i.e 500 or 404).

Related

How To Pass Parameters From A User Into An Endpoint To Process Logic From User Input

So I have a Python script that sends a get request to an API and returns information. For educational purposes, I wanted to create an API that I could pass parameters to which would return results from that script. I couldn't really find a similar example on stackoverflow or on the official flask documentation for an example that meets what I'm looking for. Everything I'm seeing is for returning data that you already have existing in a database or in a json file.
Here's an example of the script that I have right now.
script.py
api_url = www.statetaxes.com
parameters = {"taxinfo": "state:Texas county:soaker_county zip_code:78331"}
token = "abcd123456"
response= request.get(api_url, params=parameters, headers={"authentication":token})
print(response.json()) >----- [{tax rate: 1.5%, education_quality:great, crime_rate:0}]
The user would set the parameters in a config file somewhere like so:
[taxinfo]
county: some_county
zip_code: 12345
I'd want to read that text file, grab those parameters and push it to an API endpoint. I can figure out the part on how to read those parameters and send it to the API, but I'm not sure how I'm supposed to be configuring the rest of my FLASK app. I'd assume that I'd have to take those parameters which I'm reading from the text file as an argument. Am I on the right path here?
#app.route('/taxes/<taxinfo>', methods = ['GET', 'POST', 'PUT' ])
def state_taxes(taxinfo):
def api_call():
api_url = www.statetaxes.com
parameters=taxinfo
token = "abcd123456"
response= request.get(api_url, params=parameters, headers={"authentication":token})
return api_call
if __name__== "__main__":
app.run(debug=True)

Flask Middleware with both Request and Response

I want to create a middleware function in Flask that logs details from the request and the response. The middleware should run after the Response is created, but before it is sent back. I want to log:
The request's HTTP method (GET, POST, or PUT)
The request endpoint
The response HTTP status code, including 500 responses. So, if an exception is raised in the view function, I want to record the resulting 500 Response before the Flask internals send it off.
Some options I've found (that don't quite work for me):
The before_request and after_request decorators. If I could access the request data in after_request, my problems still won't be solved, because according to the documentation
If a function raises an exception, any remaining after_request functions will not be called.
Deferred Request Callbacks - there is an after_this_request decorator described on this page, which decorates an arbitrary function (defined inside the current view function) and registers it to run after the current request. Since the arbitrary function can have info from both the request and response in it, it partially solves my problem. The catch is that I would have to add such a decorated function to every view function; a situation I would very much like to avoid.
#app.route('/')
def index():
#after_this_request
def add_header(response):
response.headers['X-Foo'] = 'Parachute'
return response
return 'Hello World!'
Any suggestions?
My first answer is very hacky. There's actually a much better way to achieve the same result by making use of the g object in Flask. It is useful for storing information globally during a single request. From the documentation:
The g name stands for “global”, but that is referring to the data being global within a context. The data on g is lost after the context ends, and it is not an appropriate place to store data between requests. Use the session or a database to store data across requests.
This is how you would use it:
#app.before_request
def gather_request_data():
g.method = request.method
g.url = request.url
#app.after_request
def log_details(response: Response):
g.status = response.status
logger.info(f'method: {g.method}\n url: {g.url}\n status: {g.status}')
return response
Gather whatever request information you want in the function decorated with #app.before_request and store it in the g object.
Access whatever you want from the response in the function decorated with #app.after_request. You can still refer to the information you stored in the g object from step 1. Note that you'll have to return the response at the end of this function.
you can use flask-http-middleware for it link
from flask import Flask
from flask_http_middleware import MiddlewareManager, BaseHTTPMiddleware
app = Flask(__name__)
class MetricsMiddleware(BaseHTTPMiddleware):
def __init__(self):
super().__init__()
def dispatch(self, request, call_next):
url = request.url
response = call_next(request)
response.headers.add("x-url", url)
return response
app.wsgi_app = MiddlewareManager(app)
app.wsgi_app.add_middleware(MetricsMiddleware)
#app.get("/health")
def health():
return {"message":"I'm healthy"}
if __name__ == "__main__":
app.run()
Every time you make request, it will pass the middleware
Okay, so the answer was staring me in the face the whole time, on the page on Deferred Request Callbacks.
The trick is to register a function to run after the current request using after_this_request from inside the before_request callback. This is the code snippet of what worked for me:
#app.before_request
def log_details():
method = request.method
url = request.url
#after_this_request
def log_details_callback(response: Response):
logger.info(f'method: {method}\n url: {url}\n status: {response.status}')
These are the steps:
Get the required details from the response in the before_request callback and store them in some variables.
Then access what you want of the response in the function you decorate with after_this_request, along with the variables you stored the request details in earlier.

Is there a way to set a cookie in #before.request?

I want my application to be able to detect the user's language to serve the corresponding page to them. My idea was to use #before.request to read the Accepted-Languages header, match it against the app's supported languages, and set a cookie if needed, but it seems that the last step is not achievable.
Here is the code for that:
#app.before_request
def before_request_callback():
if request.cookies.get('lang'):
pass
else:
lang = request.accept_languages.best_match(supported_languages)
print(lang)
#I would like to set the cookie here
I thought about setting the cookie in the response object directly in the decorator (resp.set_cookie()) and thus reformatted all the returns in my app to look like this
#app.route("/")
def hello():
resp = make_response(render_template('index.html'))
return resp
to maybe be able to fetch the cookie and attach it to this response, but since the response is created afterwards in the endpoitn function, i have no idea how to do that either.
I also thought about creating the response directly in the decorator, but since i need the return condition, i don't know if thats possible
I think you're trying to use a cookie when you don't need to. As you noted, you can only set cookies on the response. You send the browser cookies in the response and then it re-sends them to you in any subsequent request. But it's already sending you the Accept-Language. So there's no point in setting a cookie on the request. It's already arrived, and already contains what you need.
Instead of getting hung up on setting cookies, just consult the request at the point that you're generating the response to ensure that the served content is in the appropriate language.
from flask import request
#app.route("/")
def hello():
lang = request.accept_languages.best_match(supported_languages)
return render_template(f'{lang}/index.html')

How to check the 'verify' setting in Python's requests using RequestBin or similar

I'm trying to make a requests session in which the verify keyword argument of each request is set to False (rather than its default value, True). I've tried the following (in Python 3):
import requests, time
requestbin_URL = 'http://requestb.in/1nsaz9y1' # For testing only; remains usable for 48 hours
class DebugSession(requests.Session):
def __init__(self, verify=False):
super().__init__()
self.verify = verify
with DebugSession() as s:
response = s.post(requestbin_URL, data={"time" : time.time()})
A request seems to have been made, as seen from http://requestb.in/1nsaz9y1?inspect:
The only problem is that I don't see how I can check whether setting verify was successful or not from the information given (namely, headers and form/post parameters). How can I check this?
You can't. You are making a request to an HTTP URL where verify=False does not have any significance. Try replacing the URL with the ones having misconfigured certifications from badssl.com (for example, https://expired.badssl.com/). If you are able to connect, it means the certificate isn't being verified.

Post print dictionary/json returns error to client

I am sending post request in the body of some json data, to process on server and I want the results back to client(c++ app on phone) in the form of json data and hence parse on mobile.
I have the following code inside handler:
class ServerHandler(tornado.web.RequestHandler):
def post(self):
data = tornado.escape.json_decode(self.request.body)
id = data.get('id',None)
#process data from db (take a while) and pack in result which is dictinary
result = process_data(id)# returns dictionary from db= takes time
print 'END OF HANDLER'
print json.dumps(result)
#before this code below I have tried also
#return result
#return self.write(result)
#return self.write(json.dumps(result))
#return json.dumps(result)
self.set_header('Content-Type', 'application/json')
json_ = tornado.escape.json_encode(result)
self.write(json_)
self.finish()
#return json.dumps(result)
I always get printed 'END OF HANDLER' and valid dictinary/json below on console but when I read at client mobile I always get
<html><title>405: Method Not Allowed</title><body>405: Method Not Allowed</body></html>
Does anyone have any idea what is the bug ?
(I am using CIwGameHttpRequest for sending request and it works when file is static =>name.json but now same content is giving error in post request. )
The error (HTTP 405 Method Not Allowed) means that you have made a request to a valid URL, but you are using an HTTP verb (e.g. GET, POST, PUT, DELETE) that cannot be used with that URL.
Your web service code appears to handle the POST verb, as evidenced by the post method name, and also by the fact that incoming requests appear to have a request body. You haven't shown us your C++ client code, so all I can do is to speculate that it is making a GET request. Does your C++ code call Request->setPOST();? (I haven't worked with CIwGameHttpRequest before, but Googling for it I found this page from which I took that line of code.)
I've not worked with Tornado before, but I imagine that there is some mechanism somewhere that allows you to connect a URL to a RequestHandler. Given that you have a 405 Method Not Allowed error rather than 404 Not Found, it seems that however this is done you've done it correctly. You issue a GET request to Tornado for the URL, it determines that it should call your handler, and only when it tries to use your handler it realises that it can't handle GET requests, concludes that your handler (and hence its URL) doesn't support GETs and returns a 405 error.

Categories