Why is python-requests to localhost slow? - python

I have a simple flask server defined like so:
import sys
import flask
from flask import request
app = flask.Flask(__name__)
port = 4057
#app.route('/search', methods=['POST'])
def search():
request.json['query']
results = ['fake', 'data']
return flask.jsonify(results)
if __name__ == '__main__':
app.config['TEMPLATES_AUTO_RELOAD'] = True
app.run(host='0.0.0.0', port=port, debug=(port != 80))
I have a simple client defined like this:
import json
import requests
headers = {'content-type': 'application/json'}
resp = requests.post('http://localhost:4057/search', json.dumps({'query': 'foo'}), headers=headers)
print resp.content
The client works, but it takes like 3 seconds to complete the request.
curl completes in like half a second:
curl 'http://localhost:4057/search' -H 'Content-Type: application/json' -d '{"query": "foo"}'

Try 127.0.0.1 There maybe some odd name resolution rules fumbling requests.
Ah ok, this is my work machine. I took a look at /etc/hosts and saw ~200 routes defined that I didn't realize were there
As mention in the comments, this does not explain the odd behavior replicated using curl.

I've recently encountered a similar issue with slow connections from requests to 'localhost', and also found that using '127.0.0.1' was much faster.
After some investigation, I found that in my case, the slow connections on Windows were related to urllib3 (the transport library used by requests) attempting an IPv6 connection first, which is refused since the server is listening on IPv4 only. For Windows only, however, there is an unavoidable 1 sec timeout on socket.connect for refused TCP connections, while the OS 'helpfully' retries the connection up to 3 times. (See: Why do failed attempts of Socket.connect take 1 sec on Windows?).
On Linux, socket.connect fails immediately if the connection is refused, and it can attempt the IPv4 connection without any delay. In addition, some software, like curl, support limiting name resolution to only IPv4 or IPv6. Unfortunately, requests and urllib3 do not support this, and do not seem to plan to support this anytime soon (See: https://github.com/psf/requests/issues/1691)

For those trying to figure this out as no one gave a clear solution here:
I encountered this issue a while ago, and noticed that the Flask test server is not concurrent. The Python Requests takes FOREVER to retrieve the data from your flask app unless you make it concurrent. Enabling "Threaded" in your app.run option will do the trick!
app.run(port=5000, debug=True, threaded=True)

Related

Cannot reach "worker" process in Heroku from web process: `ConnectionRefusedError`

I have a web process and an api process (a "worker" process similar to what is described in the docs and these other docs). However, I don't understand how the networking between different processes works.
Both processes are listening on 0.0.0.0, and the worker process is bound to port 5000. However, when I try to make a request from the web process, I get a ConnectionRefusedError:
ConnectionError: HTTPConnectionPool(host='0.0.0.0', port=5000): Max retries exceeded with url: /tokenize?sentence=hello (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7fd307019dc0>: Failed to establish a new connection: [Errno 111] Connection refused'))
Am I supposed to figure out the IP of the other process? Am I doing something wrong here?
Procfile:
api: python app.py
web: voila UI.ipynb --port $PORT --Voila.ip=0.0.0.0
app.py:
from flask import Flask, request
from ie_utils import tokenize
app = Flask(__name__)
#app.route("/")
def home():
return {
"message": "Hello world!",
"version": "0.1",
}
if __name__ == "__main__":
import os
port = 5000
app.run(host="0.0.0.0", port=port)
Relevant code in UI.ipynb:
import requests
requests.get("http://0.0.0.0:5000/")
Full source code: https://github.com/astrojuanlu/ie-titanic-utils-a/tree/test-procfile
I don't understand how the networking between different processes works
Unless you are using Private Spaces (part of Heroku's enterprise offering), it doesn't:
The Common Runtime provides strong isolation by firewalling all dynos off from one another. The only traffic that can reach a dyno is web requests forwarded from the router to web processes listening on the port number specified in the $PORT environment variable. Worker and one-off dynos cannot receive inbound requests.
I'm not totally clear what you're trying to do here, but you won't be able to do it by making an HTTP request to localhost. You might have better luck running this as two separate apps—in that case your API could be its own web process and your Voilà app could make requests to the API app's hostname.
Side note: even on a system where this is permitted your request should not go to 0.0.0.0. That isn't a real IP address.
Telling Flask to bind to 0.0.0.0 really means that it should listen on all available IP addresses. When you want to make a request you should use one of the real hostnames or IP addresses that your system is using, e.g. localhost or 127.0.0.1 or one of its public IP addresses or hostnames.

flask server with ssl_context freezes if it receives http request

I'm trying to create a simple flask server that redirects any http requests to https. I've created a certificate and key file and registered a before_request hook to see if the request is secure and redirect appropriately, following advise this SO answer.
The flask server responds to https requests as expected. However, when I send an http request, the before_request hook never gets called and ther server hangs forever. If I send the http request from the browser, I see an "ERR_EMPTY_RESPONSE". The server doesn't even respond to https requests afterwards. No logs are printed either.
Running the app with gunicorn didn't help either. The only difference was that gunicorn is able to detect that the worker is frozen and eventually kills and replaces it. I've also tried using flask-talisman, with the same results.
Below is the code I'm running
### server.py
from flask import Flask, request, redirect
def verify_https():
if not request.is_secure:
url = request.url.replace("http://", "https://", 1)
return redirect(url, 301)
def create_flask_app():
app = Flask(__name__)
app.before_request(verify_https)
app.add_url_rule('/', 'root', lambda: "Hello World")
return app
if __name__ == '__main__':
app = create_flask_app()
app.run(
host="0.0.0.0",
port=5000,
ssl_context=('server.crt', 'server.key')
)
Running it with either python3.8 server.py or gunicorn --keyfile 'server.key' --certfile 'server.crt' --bind '0.0.0.0:5000' 'server:create_flask_app()' and opening a browser window to localhost:5000 causes the server to hang.
Talking about freezes, its not. Flask and gunicorn can serve only one variant of connection. So it's not freezing because your browser canceled the request and is idling.
I think it is better to use a faster web server, for example, Nginx, if you want to change HTTP to HTTPS. I would recommend it to you.
But it's possible to trigger your verify_https function if you run multiple instances of gunicorn at the same time.
I took your example, generated a certificate, and then run this script in my console (it contains a background job and can be runned in twoo separate ter)
gunicorn --bind '0.0.0.0:80' 'server:create_flask_app()' & gunicorn --certfile server.crt --keyfile server.key --bind '0.0.0.0:443' 'server:create_flask_app()'
now chrome goes to the secure page as expected.
Typically servers don't listen for both http and https on the same port. I have a similar requirement for my personal portfolio, but I use nginx to forward http requests (port 80) to https (port 443) and then the https server passes it off to my uwsgi backend, which listens on port 3031. That's probably more complex than you need, but a possible solution. If you go that route I would recommend letsencrypt for your certificate needs. It will set up the certificates AND the nginx.conf for you.
If you don't want to go the full nginx/apache route I think your easiest solution is the one suggested here on that same thread that you linked.

sending a python request to a different host on the same network

Currently I have something called a client, and a server.py that is running a flask server. I also have a different machine running a different flask server. (With the host being the ip address of the machine)
on one of the app routes, it will reroute the request to the different machine (Because of reasons) to process
Essentially, it looks something like this
server.py
#app.route('/endpointapi', methods = ['POST'])
def doStuff():
f = request.form['message']
correctURL = "http://somenumber.2.3:port/endpointapi"
r = requests.post(correctURL, data={'message', f})
return r
differentMachineOneSameNetwork.py
#app.route('/endpointapi', methods = ['POST'])
def doStuff():
//does things correctly
return updated message
The somenumber.2.3 is a valid ip address of the machine that is running differentMachineOneSameNetwork.py along with the port. Both machines are running a flask webserver, but the client can only send requests to the server.py. However, it is not connecting. Is there any reason why it might not be working?
EDIT
The ports are artifically chosen. 5555 for the server.py, and 8090 for the different machine. It works when we just test connections from the same machine to its own using curl commands, but it does not work when we try to curl from server machine to the other machine.
Whats working:
Curl from server to server flask
Curl from differentMachine to differentMachine flask
Curl from differentMachine to server flask
Not working:
Curl from server to differentMachine doesn't work. Connection just times out. Example command that was sent:
curl -X POST -F "message=some bojangle" http://correct.ip.address:8090/endpointapi
I get:
curl: (7) Failed to connect to correct.ip.address port 8090: Connection timed out
It just times out after trying to establish a connection, giving http response code of 500.

printing URL parameters of a HTTP request using Python + Nginx + uWSGI

I have used this link and successfully run a python script using uWSGI. Although i just followed the doc line by line.
I have a GPS device which is sending data to a remote server. Document of the same device say that it connect to server using TCP which therefore would be http as simple device like a GPS device would not be able to do https (i hope i am right here.) Now as i have configure my Nginx server to forward all incoming HTTP request to python script for processing via uWSGI.
What i want to do is to simply print the url or query string on the HTML page. As i don't have control on the device side (i can only configure device to send data on a IP + Port), i have no clue how data is coming. Below is my access log
[23/Jan/2016:01:50:32 +0530] "(009591810720BP05000009591810720160122A1254.6449N07738.5244E000.0202007129.7200000000L00000008)" 400 172 "-" "-" "-"
Now i have look at this link on how to get the url parameters values, but i don't have a clue that what is the parameter here.
I tried to modified my wsgi.py file as
import requests
r = requests.get("http://localhost.com/")
# or r = requests.get("http://localhost.com/?") as i am directly routing incoming http request to python script and incoming HTTP request might not have #any parameter, just data #
text1 = r.status_code
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return ["<h1 style='color:blue'>Hello There shailendra! %s </h1>" %(text1)]
but when i restarted nginx, i get internal server error. Can some one help me to understand wrong i am doing here (literally i have no clue about the parameters of the application function. Tried to read this link, but what i get from here is that environ argument take care of many CGI environment variables.)
Can some one please help me to figure out what wrong am i doing and guide me to even a doc or resource.
Thanks.
why are you using localhost ".com" ?
Since you are running the webserver on the same machine,
you should change the line to
r = requests.get("http://localhost/")
Also move below lines from wsgi.py and put them in testServerConnection.py
import requests
r = requests.get("http://localhost/")
# or r = requests.get("http://localhost.com/?") as i am directly routing incoming http request to python script and incoming HTTP request might not have #any parameter, just data #
text1 = r.status_code
Start NGINX
and you also might have to run (i am not sure uwsgi set up on nginx)
uwsgi --socket 0.0.0.0:8080 --protocol=http -w wsgi
run testConnection.py to send a test request to localhost webserver and print the response
i got the answer for my question. Basically to process a TCP request, you need to open a socket and accept the TCP request on a specific port (as you specified on the hardware)
import SocketServer
class MyTCPHandler(SocketServer.BaseRequestHandler):
def handle(self):
# self.request is the TCP socket connected to the client
self.data = self.request.recv(1024).strip()
print "{} wrote:".format(self.client_address[0])
#data which is received
print self.data
if __name__ == "__main__":
#replace IP by your server IP
HOST, PORT = <IP of the server>, 8000
# Create the server, binding to localhost on port 9999
server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler)
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()
After you get the data, you can do any thing with the data. As my data format was shared in the GPS datasheet, i was able to parse the string and get the Lat and long out of it.

Listening for HTTP responses

I am trying to write a python application that will listen for HTTP responses on a socket. I am using http-parser for this. Here is my code:
#!/usr/bin/env python
import socket
from http_parser.http import HttpStream
from http_parser.reader import SocketReader
from http_parser.util import b
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((socket.gethostname(), 7000))
s.listen(5)
try:
while True:
p = HttpStream(SocketReader(s))
finally:
s.close()
if __name__ == "__main__":
main()
I have two questions:
Is this the best way to do this? Note that I do not want to send a request and then listen for a response. I want to have a redirection mechanism that redirects all responses to the box running this script.
How should I test this? Is there a tool that can mock HTTP responses?
EDIT
What I am trying to do is this: I have three boxes, once runs Apache, one runs this script and one is the client. When the client connects to Apache and it sends back a response, I am diverting the response to this box. So in this script, I am trying to listen for HTTP responses.
Topology
Here is my topology:
Server <----> Switch one <-----> Switch two <-----> Box one and two
Initially, box one connects to the server and sends a request. When the second switch receives responses from the server, it forks it to both box one and box two.
That is a perfectly fine implementation if you really want to operate at the level of TCP sockets. If you want more abstraction, there are lots and lots of HTTP server packages for python, including the standard library's BaseHttpServer, and external libraries/frameworks like tornado and cherrypy.
To test your HTTP listener there are lots of options. You could write full-on HTTP client test code in Python (using an HTTP client library like urllib), or you could:
Point your web browser to http://localhost:7000
telnet to port 7000 on localhost and type in raw HTTP requests.
Automate the above by using nc, e.g.:
echo -e 'GET / HTTP/1.1\n\n' | nc localhost 7000
A note on terminology: What you are listening for on your bound socket is an HTTP request, in the HTTP parlance; what you send back to the connecting client is a response.
1. Is there is a better way?
Yes, there is
2. How should I test this?
common practice is to put a test.py in the same folder, and run python test.py to test. Sample code:
#!python
# -*- coding: utf-8 -*-
import optparse
import urllib2
from django.utils import unittest
# just a sample of settings
HOST = 'localhost'
class GIOPTest(unittest.TestCase):
def test_basic(self):
#TEST GOES HERE
if __name__ == '__main__':
# This part is to accept command line parameters
option_list = ( # check optparse help for more options
make_option("--host",
action = 'store',
type = 'string',
dest = 'host',
default = 'localhost',
help = 'Server host (localhost by default)'
),
)
parser = OptionParser(option_list=option_list)
options, args = parser.parse_args()
HOST = options.host
# run the test
unittest.main()
I ended up using a raw socket and directly reading packets from it.
if you're using apache as the server you're redirecting the data to you could use apache benchmark (ab - you must be superuser though to use it as far as I know) to test it... will also help evaluate the performance impact your application has on the whole thing, example
ab -n 500 -c 20 http://localhost:7000/
where after -n is the total number of connections made to the server during the test and after -c you have the number of concurrent connections apache benchmark will make, experiment with different values of these, also read it's manual, you might find more useful options for whatever your specific purpose might be

Categories

Resources