Replace server name with fake server name in response header in fastapi - python-3.x

I am using uvicorn as server to run app using fast api. While executing endpoint url in Swagger, following message is shown in response header of server response.
content-length: 122
content-type: application/json
date: Sat12 Dec 2020 10:18:55 GMT
server: uvicorn
How to change server name to new name as server : firstproject?
Following code concatenates server name unciorn with new name
#app.middleware("http")
async def add_custom_header(request, call_next):
response = await call_next(request)
response.headers['server'] = 'firstproject'
return response
This gives the following output
content-length: 122
content-type: application/json
date: Sat12 Dec 2020 10:19:33 GMT
server: uvicornfirstproject
How to change server name to server : firstproject in response header?
EDIT
In start_server.py
import uvicorn
from app.main import app
if __name__ == "__main__":
uvicorn.run("start_server:app --header server:firstproject", host="0.0.0.0", port=8000, reload=True)
gives following error
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: Started reloader process [15256] using statreload
ERROR: Error loading ASGI app. Attribute "app --header server:firstproject" not found in module "start_server".
I run the code from Visual studio

You can set a custom header when running uvicorn.
--header TEXTSpecify custom default HTTP response headers as a Name:Value pair
When you run it like this, it will override the default server name.
uvicorn my_app:app --header server:firstproject
If you are running uvicorn from a python file. You need to pass them as tuple inside a list.
if __name__ == "__main__":
uvicorn.run("my_app:app", headers=[("server", "firstproject")])

In case of you need to "delete" the "server" header, you can use option --no-server-header
uvicorn my_app:app --no-server-header
If you are running uvicorn from a python file:
if __name__ == '__main__':
uvicorn.run('my_app:app', server_header=False)

Related

FastAPI gives error 405 method not allowed while running the application as Python wheel

I am working on Fast API project based on Angular UI. Before wheel package converted it is been working fine. So now when I convert the application into .whl package using below command:
python3.9 setup.py bdist_wheel
wheel file get converted also it run the UI server on http://127.0.0.1:8000/ successfully but it does not call api endpoints and throws error "INFO: 127.0.0.1:54394 - "POST /api/devices/connect HTTP/1.1" 405 Method Not Allowed"
Content of files main.py is as below:
app = FastAPI(
title="ProjectName",
description="ProjectName Description",
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
static_dir = os.path.join(os.path.dirname(__file__), "static")
app.mount("/", StaticFiles(directory=static_dir, html=True), name="static")
#app.post("/api/devices/connect")
async def auth(auth_input: AuthInput):
print('API called here.....')
## other codes goes here
def start_server():
print("Starting Server...Success")
webbrowser.open("http://127.0.0.1:8000/")
uvicorn.run(
"projectname.main:app",
host="0.0.0.0",
port=8000,
log_level="debug",
reload=True,
)
I run the python wheel file as below:
pip install --extra-index-url projectName.whl
Also I tried running the API on http://127.0.0.1:8000/docs but it throws same error 405 method not allowed.
How can I run this API, its a POST method. And running this api on http://127.0.0.1:8000/docs gives me attached error:

How to run an XMLRPC server and an XMLRPC client on Mininet hosts through a python script?

I am trying to run an XMLRPC server and an XMLRPC client on Mininet hosts, using the script below.
from mininet.topo import Topo
from mininet.net import Mininet
from mininet.node import OVSController
class MyTopo(Topo):
def __init__(self):
# Initialize topology
Topo.__init__(self)
# Add hosts
server1 = self.addHost('server1')
server2 = self.addHost('server2')
# Add switch
s1 = self.addSwitch('s1')
# Add links
self.addLink(server1, s1)
self.addLink(server2, s1)
if __name__ == '__main__':
net = Mininet(topo=MyTopo(), controller=OVSController)
net.start()
print(net.hosts[0].cmd('python3 xmlrpc_server.py'))
print(net.hosts[1].cmd('python3 xmlrpc_client.py'))
The file xmlrpc_server.py is:
from xmlrpc.server import SimpleXMLRPCServer
import threading
def is_even(n):
return n%2 == 0
server = SimpleXMLRPCServer(("0.0.0.0", 8000), logRequests=True, allow_none = True)
server.register_function(is_even, "is_even")
print("Listening on port 8000...")
server_thread = threading.Thread(target=server.serve_forever)
server_thread.start()
The file xmlrpc_client.py is:
import xmlrpc.client
proxy = xmlrpc.client.ServerProxy("http://10.0.0.1:8000/")
print("3 is even: %s" % str(proxy.is_even(3)))
print("100 is even: %s" % str(proxy.is_even(100)))
The problem is that although I have used a thread, when I run the xmlrpc_server.py script on server1, the execution pauses at line server_thread.start() waiting for the script execution to be completed before moving on and thus never goes on to the next line, which means that the XMLRPC client script never runs. How do I overcome this problem?
P.S.: xmlrpc_server.py and xmlrpc_client.py can be executed through the server terminals (by using the commands xterm server1 and xterm server2 on Mininet CLI and then using the commands python3 xmlrpc_server.py and python3 xmlrpc_client.py on the xterm terminals that open), but I need to start the server and client through a python script so as to perform some further calculations after the communication between the two servers.
Replace print(net.hosts[0].cmd('python3 xmlrpc_server.py')) with print(net.hosts[0].sendCmd('python3 xmlrpc_server.py')). Connection is sometimes refused, but that issue can be resolved with exception handling on the client script.

Directadmin upload logo using CMD_SKINS with error cannot get mime-type

I'm trying to upload a custom logo using CMD_API_SKINS
Here is my full code using curl + bash:
#/bin/bash
# DA needs this path
mkdir -p /home/tmp
# Assume my logo file is already here:
default_logo_file_home="/home/tmp/logo.png"
# The logo file is set to nobody:nogroup
chown nobody:nogroup "${default_logo_file_home}"
## Setup query data for curl:
username="admin"
password="12321aa"
da_port="2222"
host_server="server.domain.com"
ssl="https"
skin_name="evolution"
command="CMD_API_SKINS"
data="action=upload_logo&file=${default_logo_file_home}&json=yes&name=${skin_name}&which=1"
method="POST"
curl --silent --request "${method}" --user "${username}":"${password}" --data "${data}" "${ssl}://${host_server}:${da_port}/${command}"
When debugging this API, I got an error like this:
text='An Error Occurred'
result='Cannot get mime-type for log<br>
It seems like DA is trying to parse and obtain the extension name for the file "logo.png" but it couldn't
Full error logs:
DirectAdmin 1.61.5
Accepting Connections on port 2222
Sockets::handshake - begin
Sockets::handshake - end
/CMD_API_SKINS
0: Accept: */*
1: Authorization: Basic bWF4aW93bng3OnhGVEVHe***jUSg/UTRTfVdHYW0+fWNURn5ATWN***HFbZGpMezlQZ***=
2: Content-Length: 75
3: Content-Type: application/x-www-form-urlencoded
4: Host: server.domain.com:2222
5: User-Agent: curl/7.75.0
Post string: action=upload_logo&file=/home/tmp/logo2.png&json=yes&name=evolution&which=2
auth.authenticated
User::deny_override:/CMD_API_SKINS: call_level=2, depth1: aborting due to do depth
User::deny_override:/CMD_DOMAIN: call_level=2, depth1: aborting due to do depth
User::deny_override:/CMD_DOMAIN: call_level=1, depth2: aborting due to do depth
Plugin::addHooks: start
Plugin::addHooks: end
Command::doCommand(/CMD_API_SKINS)
cannot get mime type for log
Dynamic(api=1, error=1):
text='An Error Occurred'
result='Cannot get mime-type for log<br>
'
Command::doCommand(/CMD_API_SKINS) : finished
Command::run: finished /CMD_API_SKINS
I also try to encode the query like this but still got the same error
default_logo_file_home="%2Fhome%2Ftmp%2Flogo%2Epng"
data="action=upload%5Flogo&file=${default_logo_file_home}&json=yes&name=${skin_name}&which=%31"
Is there any explanations what is going on here? Is it possible to upload a logo using this API ?
Ok, I found the trick which is not documented anywhere. The uploaded file need to have a random string append to it. So I changed this:
default_logo_file_home="/home/tmp/logo.png"
into this:
RANDOM_STR="EbYIES"
default_logo_file_home="/home/tmp/logo.png${RANDOM_STR}"
Now it's working perfectly

Python, Flask print to console and log file simultaneously

I'm using python 3.7.3, with flask version 1.0.2.
When running my app.py file without the following imports:
import logging
logging.basicConfig(filename='api.log',level=logging.DEBUG)
Flask will display relevant debug information to console, such as POST/GET requests and which IP they came from.
As soon as DEBUG logging is enabled, I no longer receive this output. I have tried running my application in debug mode:
app.run(host='0.0.0.0', port=80, debug=True)
But this produces the same results. Is there a way to have both console output, and python logging enabled? This might sound like a silly request, but I would like to use the console for demonstration purposes, while having the log file present for troubleshooting.
Found a solution:
import logging
from flask import Flask
app = Flask(__name__)
logger = logging.getLogger('werkzeug') # grabs underlying WSGI logger
handler = logging.FileHandler('test.log') # creates handler for the log file
logger.addHandler(handler) # adds handler to the werkzeug WSGI logger
#app.route("/")
def index():
logger.info("Here's some info")
return "Hello World"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
Other Examples:
# logs to console, and log file
logger.info("Some text for console and log file")
# prints exception, and logs to file
except Exception as ue:
logger.error("Unexpected Error: malformed JSON in POST request, check key/value pair at: ")
logger.error(ue)
Source:
https://docstrings.wordpress.com/2014/04/19/flask-access-log-write-requests-to-file/
If link is broken:
You may be confused because adding a handler to Flask’s app.logger doesn’t catch the output you see in the console like:
127.0.0.1 - - [19/Apr/2014 18:51:26] "GET / HTTP/1.1" 200 -
This is because app.logger is for Flask and that output comes from the underlying WSGI module, Werkzeug.
To access Werkzeug’s logger we must call logging.getLogger() and give it the name Werkzeug uses. This allows us to log requests to an access log using the following:
logger = logging.getLogger('werkzeug')
handler = logging.FileHandler('access.log')
logger.addHandler(handler)
# Also add the handler to Flask's logger for cases
# where Werkzeug isn't used as the underlying WSGI server.
# This wasn't required in my case, but can be uncommented as needed
# app.logger.addHandler(handler)
You can of course add your own formatting and other handlers.
Flask has a built-in logger that can be accessed using app.logger. It is just an instance of the standard library logging.Logger class which means that you are able to use it as you normally would the basic logger. The documentation for it is here.
To get the built-in logger to write to a file, you have to add a logging.FileHandler to the logger. Setting debug=True in app.run, starts the development server, but does not change the log level to debug. As such, you'll need to set the log level to logging.DEBUG manually.
Example:
import logging
from flask import Flask
app = Flask(__name__)
handler = logging.FileHandler("test.log") # Create the file logger
app.logger.addHandler(handler) # Add it to the built-in logger
app.logger.setLevel(logging.DEBUG) # Set the log level to debug
#app.route("/")
def index():
app.logger.error("Something has gone very wrong")
app.logger.warning("You've been warned")
app.logger.info("Here's some info")
app.logger.debug("Meaningless debug information")
return "Hello World"
app.run(host="127.0.0.1", port=8080)
If you then look at the log file, it should have all 4 lines printed out in it and the console will also have the lines.

Spin up a local flask server for testing with pytest

I have the following problem.
I'd like to run tests on the local flask server before deploying to production. I use pytest for that. My conftest.py looks like that for the moment:
import pytest
from toolbox import Toolbox
import subprocess
def pytest_addoption(parser):
"""Add option to pass --testenv=local to pytest cli command"""
parser.addoption(
"--testenv", action="store", default="exodemo", help="my option: type1 or type2"
)
#pytest.fixture(scope="module")
def testenv(request):
return request.config.getoption("--testenv")
#pytest.fixture(scope="module")
def testurl(testenv):
if testenv == 'local':
return 'http://localhost:5000/'
else:
return 'https://api.domain.com/'
This allows me to test the production api by typing the command pytest and to test a local flask server by typing pytest --testenv=local
This code WORKS flawlessly.
My problem is that I have to manually instantiate the local flask server from the terminal each time I want to test locally like this:
source ../pypyenv/bin/activate
python ../app.py
Now I wanted to add a fixture that initiates a terminal in the background at the beginning of the tests and closes the server down after having finished testing. After a lot of research and testing, I still cannot get it to work. This is the line I added to the conftest.py:
#pytest.fixture(scope="module", autouse=True)
def spinup(testenv):
if testenv == 'local':
cmd = ['../pypyenv/bin/python', '../app.py']
p = subprocess.Popen(cmd, shell=True)
yield
p.terminate()
else:
pass
The errors I get are from the requests package that says that there is no connection/ refused.
E requests.exceptions.ConnectionError:
HTTPConnectionPool(host='localhost', port=5000): Max retries exceeded
with url: /login (Caused by
NewConnectionError(': Failed to establish a new connection:
[Errno 111] Connection refused',))
/usr/lib/python3/dist-packages/requests/adapters.py:437:
ConnectionError
This means for me that the flask server under app.py is not online. Any suggestions? I am open to more elegant alternatives
For local testing the Flask test_client is a more elegant solution. See the docs on Testing. You can create a fixture for the test_client and create test requests with that:
#pytest.fixture
def app():
app = create_app()
yield app
# teardown here
#pytest.fixture
def client(app):
return app.test_client()
And use it like this:
def test_can_login(client):
response = client.post('/login', data={username='username', password='password'})
assert response.status_code == 200
If the only problem are the manual steps, maybe consider a bash script that does your manual setup for you and after that executes pytest.
I am using the following for this purpose so that testing configuration is also preserved in the test server
#pytest.fixture(scope="session")
def app():
db_fd, db_path = tempfile.mkstemp()
app = create_app({
'TESTING': True,
'DATABASE': db_path
})
yield app
os.close(db_fd)
os.unlink(db_path)
from flask import request
def shutdown_server():
func = request.environ.get('werkzeug.server.shutdown')
if func is None:
raise RuntimeError('Not running with the Werkzeug Server')
func()
#pytest.fixture
def server(app):
#app.route('/shutdown',methods=('POST',))
def shutdown():
shutdown_server()
return 'Shutting down server ...'
import threading
t = threading.Thread(target=app.run)
yield t.start()
import requests
requests.post('http://localhost:5000/shutdown')
References
https://flask.palletsprojects.com/en/1.1.x/tutorial/tests/
How do I terminate a flask app that's running as a service?
How to stop flask application without using ctrl-c
With a bash script (thanks #ekuusela) I now finally succeeded in what I wanted.
I added a fixture that calls the bashscript spinserver.sh in a new terminal window. This works in ubuntu, the command is different in different environments (see Execute terminal command from python in new terminal window? for other environments).
#pytest.fixture(scope="session", autouse=True)
def client(testenv):
if testenv != 'local':
pass
else:
p = subprocess.Popen(['gnome-terminal', '-x', './spinserver.sh'])
time.sleep(3)
yield
Here the very simple bashscript
#!/bin/bash
cd ..
source pypyenv/bin/activate
python app.py
The sleep command is necessary because the server takes some time to
initialize.
Don't forget to make your bash script executable (chmod
u+x spinserver.sh)
I tried to do a teardown after yield, but p.kill does not really
close the window. This is acceptable for me as it does not matter
if I have to manually close a terminal window & I can even see
flask debugging if necessary

Resources