Long time to first byte for my Flask app homepage - flask

I have a Flask site deployed to IIS via wfastcgi configuration.
When I use chrome or firefox developer tools to analyse the loading time of the homepage, I find many seconds (ranging from 6 to 10 in average) as waiting time to receive the first byte.
It was even 30 seconds before, but then I "optimized" my python code to avoid any db sql operation at loading time. Then I've followed the hints of this blog of nspointers, and now from the taskbar of the server I see the w3wp.exe for my app pool identity
w3wp.exe – It is the IIS worker process for the application pool
staying up and running even during idle time. But that is not true for the other
python.exe – The main FastCGI process for the Django or flask
applications.
and I'm not sure if this is a problem and just in case what I am supposed to do, aside from the step 4 described in the mentioned post.
Now in the “Edit FastCGI Application” dialog under “Process Model”
edit the “Idle Timeout” and set it to 2592000 which is the max value
for that field in seconds
I've also looked at the log written by the Flask app and compared it to the log written by IIS and this is the most important point in making me believe that the issue is in the wfastcgi part, before the execution of the python code.
Because I see that the time-taken of the IIS log matches with the client time reported by chrome or firefox as TTFB and the log written by python at the start of the execution is logged at almost the same time of the time written by IIS, that
corresponds to the time that the request finished
(as I thought indeed and as I find it's confirmed by this answer)
So in conclusion, based on what I tried and what I understand, I suspect that IIS is "wasting" many seconds to "prepare" the python wfascgi command, before actually starting to execute my app code to produce a response for the web request. It is really too much in my opinion, since other applications I've developed (for example in F# WebSharper) under IIS without this mechanism of wfastcgi load immediately in the browser and the difference in the response time between them and the python Flask app is quite noticeable. Is there anything else I can do to improve the response time?

Ok, now I have the proof I was searching and I know where the server is actually spending the time.
So I've researched a bit about the wfastcgi and finally opened the script itself under venv\Lib\site-packages.
Skimming over the 900 lines, you can spot the relevant log part:
def log(txt):
"""Logs messages to a log file if WSGI_LOG env var is defined."""
if APPINSIGHT_CLIENT:
try:
APPINSIGHT_CLIENT.track_event(txt)
except:
pass
log_file = os.environ.get('WSGI_LOG')
if log_file:
with open(log_file, 'a+', encoding='utf-8') as f:
txt = txt.replace('\r\n', '\n')
f.write('%s: %s%s' % (datetime.datetime.now(), txt, '' if txt.endswith('\n') else '\n'))
Now, well knowing how to set the environment variables, I defined a specific WSGI_LOG path, and here we go, now I see those 5 seconds TTFB from chrome (as well as the same 5 seconds from IIS log with time 11:23:26 and time-taken 5312) in the wfastcgi.py log.
2021-02-01 12:23:21.452634: wfastcgi.py 3.0.0 initializing
2021-02-01 12:23:26.624620: wfastcgi.py 3.0.0 started
So, of course, wfastcgi.py is the script one would possibly try to optimize...
BTW, after digging into it, that time is due to importing the main flask app
handler = __import__(module_name, fromlist=[name_list[0][0]])
What remains to be verified is the behavior of rerunning the process (and the import of the main flask module, that is time consuming) for each request.
In conclusion, I guess it is a BUG, but I have solved it by deleting the "monitoring changes to file" FastCGI settings as per the screenshot below.
The response time is under a second.

I have a different answer to you by suggesting you try to switch over to HTTP Platform Handler for your IIS fronted Flask app.
Config Reference
This is also the recommended option by Microsoft:
Your app's web.config file instructs the IIS (7+) web server running on Windows about how it should handle Python requests through either HttpPlatform (recommended) or FastCGI.
https://learn.microsoft.com/en-us/visualstudio/python/configure-web-apps-for-iis-windows?view=vs-2019
Example config can be:
<configuration>
<system.webServer>
<handlers>
<add name="httpplatformhandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/>
</handlers>
<httpPlatform processPath="c:\inetpub\wwwroot\run.cmd"
arguments="%HTTP_PLATFORM_PORT%"
stdoutLogEnabled="true"
stdoutLogFile="c:\inetput\Logs\logfiles\python_app.log"
processPerApplication="2"
startupTimeLimit="60"
requestTimeout="00:01:05"
forwardWindowsAuthToken="True"
>
<environmentVariables>
<environmentVariable name="FLASK_RUN_PORT" value="%HTTP_PLATFORM_PORT%" />
</environmentVariables>
</httpPlatform>
</system.webServer>
</configuration>
With run.cmd being something like
cd %~dp0
.venv\scripts\waitress-serve.exe --host=127.0.0.1 --port=%1 myapp:wsgifunc
Note that the HTTP Platform handler will dynamically set on a port and passing that into the python process via the FLASK_RUN_PORT env var which flask will automatically take as a port configuration.
Security notes:
Make sure you bind your flask app to localhost only, so it's not visible directly from the outside - especially if you are using authentication via IIS
In the above example the forwardWindowsAuthToken is being set which then can be used to rely on Windows Integrated authentication done by IIS then the token passed over to Python and you can get the authenticated user name from Python. I have documented that here. I actually use that for single-sign on with Kerberos and AD group based authorization, so it works really nice.
Example to only listen on localhost / loopback adapter to avoid external requests hitting the python app directly. In case you want all requests to go via IIS.
if __name__ == "__main__":
app.run(host=127.0.0.1)

Related

Running flask server on google colab through ngrok flask limiter not working

I am running a debug flask server on a google colab notebook through ngrok, i followed this article mainly: https://medium.com/#kshitijvijay271199/flask-on-google-colab-f6525986797b
I called this endpoint in a loop and pretty soon, a few number of requests came back with status 429. Next, I installed flask limiter, and tried to exempt that endpoint so I can send in high frequency requests. This didn't seem to work as I still got 429. I double checked the code for that flask limiter and couldn't spot any mistakes.
So I begin to think it may be the ngrok tunnel that's raising the 429 on my behalf (or overriding me)? Has anyone run into this and know for sure ngrok put a limit on you regardless of how you configure flask limiter?
I can provide code snippet upon request. But i hope anyone with more experience using the ngrok service can give me a feedback. (I will soon migrate to a permanent VM instead of colab and probably will find out soon and report back in case no one answers).
This seems to have something to do with ngrok. Once I switched to a permanent VM and run it directly (without going through ngrok), there's no problem and the no limit seems to be respected.

CSRF Token Error when using gunicorn and Flask

I've developed a web app that includes the functionality for users to log in and signup. I've done everything as per documentation and tutorials, and everything works fine with the flask server.
The issue is: I use gunicorn and start a web server, open the address (localhost:8000) on few different browsers (Brave and Firefox on Linux, and Brave on Android), I can only log in (Single or multiple different users) from only one client. When I try to do so from another one, it throws 400 Bad Request (CSRF Session token missing or CSRF Session tokens do no match).
Now, this doesn't happen when using Flasks Server. It only happens while using gunicorn.
I've deployed the application to Heroku (Using the Free plan), and I want multiple users to sign in at the same time. This happens on all of the other forms my app has.
All environment variables, secret keys are correctly configured, everything works as intended when using Flask Server. Using Gunicorn doesn't cause any other issues except this. Sometimes, logging in from a single client doesn't work too.
Any help would be appreciated. I've already looked at other threads/questions that were related, but they didn't mention the problem I have
Sorry for the late reply (Maybe it can help someone in the future)
The short answer :
use :
with app.app_context():
your code
instead of :
app.app_context().push()
which is never closed
The long answer :
I guess you use Flask-WTF for managing CSRF,
If yes, there is an if bloc (https://github.com/wtforms/flask-wtf/blob/0.15.x/src/flask_wtf/csrf.py#L53) in the generate_csrf function, which check the flask g variable before generating a new CSRF token. It works perfectly, but when the g variable doesn't reinitilize on each new request (see below for the cause) it creates a conflict between the different users trying to log in.
The principal cause of not reinitilizing the flask g variable is an app context which is pushed manually (usually by : app.app_context().push()) but not closed, in this case the app context is never torn down see : Flask app.teardown_appcontext not being called when DEBUG is false
Finally, i think there is something in the flask DEBUG mode, which force tearing down the app context and reinitilize the g variable.

Cookies when using separate Dyno for react frontend and Django backend

I am building a simple web app using React.js for the frontend and Django for the server side.
Thus frontend.herokuapp.com and backend.herokuapp.com.
When I attempt to make calls to my API through the react app the cookie that was received from the API is not sent with the requests.
I had expected that I would be able to support this configuration without having to do anything special since all server-side requests would (I thought) be made by the JS client app directly to the backend process with their authentication cookies attached.
In an attempt to find a solution that I thought would work I attempted to set
SESSION_COOKIE_DOMAIN = "herokuapp.com"
Which while less than ideal (as herokuapp.com is a vast domain) in Production would seem to be quite safe as they would then be on api.myapp.com and www.myapp.com.
However, with this value set in settings.py I get an AuthStateMissing when hitting my /oauth/complete/linkedin-oauth2/ endpoint.
Searching google for AuthStateMissing SESSION_COOKIE_DOMAIN yields one solitary result which implies that the issue was reported as a bug in Django social auth and has since been closed without further commentary.
Any light anyone could throw would be very much appreciated.
I ran into the exact same problem while using herokuapp.com.
I even posted a question on SO here.
According to Heroku documentation:
In other words, in browsers that support the functionality, applications in the herokuapp.com domain are prevented from setting cookies for *.herokuapp.com
Heroku blocks cookies from frontend.herokuapp.com and backend.herokuapp.com
You need to add a custom domain to frontend.herokuapp.com and backend.herokuapp.com
The entire answer https://stackoverflow.com/a/54513216/1501643

JMeter - cookies no clearing on each new test run

I'm building a test in Jmeter and have hit a seemingly trivial problem, but I cannot find a way to overcome it.
I need to start a new session each time the test runs (I'm building in the GUI). I thought that setting the HTTP cookie manager in the Thread Group with the 'Clear cookies on each iteration' setting checked would clear the cookies on each new run of the test.
It doesn't seem to work however - when running a test for the second time the user session initiated in the previous run is still there.
In fact I am now getting a 494 server error (Cookie too large) because the cookies are accruing in size with each run of the test.
Any ideas on how to clear the cookies with each new run of the test?
As per your comment, issue was related to environment and not JMeter.
Most probably Cookies we stacking up due to some infinite redirection or similar environment problem.
If you face any issue with Cookie Manager, refer to reference documentation.
I doubt this is a JMeter problem, most likely you're receiving an incorrect cookie via Set-Cookie header from the system under test.
You can troubleshoot this issue by enabling extra logging for JMeter's HTTP Cookie manager, this can be done by adding the next line to log4j2.xml file (lives in "bin" folder of your JMeter installation)
<Logger name="org.apache.jmeter.protocol.http.control.CookieManager" level="debug" />
JMeter restart will be required to pick up the changes
Also be aware that you can manually remove all stored cookies in an arbitrary place of your script
Add JSR223 PreProcessor as a child of the HTTP Request sampler
Put the following code into "Script" area:
sampler.getCookieManager().clear()

Continuous CONNECT/GET requests to web server on port 9485

I've tried using both CherryPy 3.5 and Tornado 4 to host my Flask WSGI application. Regardless of the WSGI server, every 2s my browser tries to make a CONNECT/GET request to myserver:9485.
Chrome's network view looks like this:
Firefox's network view looks like this:
Finally, IE's network view doesn't show anything, but I do see this in the console:
Everything about the site works fine, but it kills me knowing this is happening in the background. How do I stop the madness?
Oddly enough, I don't see these messages when I run the code shown below on my development machine.
Additional details:
Python 2.7.6, Flask 0.10.1, Angular 1.2.19
CherryPy code:
static_handler = tools.staticdir.handler(section='/', dir=app.static_folder)
d = wsgiserver.WSGIPathInfoDispatcher({'/': app, '/static': static_handler})
server = wsgiserver.CherryPyWSGIServer(('127.0.0.1', 9000), d)
server.start()
Probably unrelated, but I am using an EventSource polyfill on one of my pages (I see these messages regardless if I hit that page or not).