I have a web flask application I have deployed using Windows IIS.
I am able to access the website and it shows up but when I click on the "start button" I have built within the website, which causes a series of API calls using the requests library. I am seeing that the data is not being "GET" or "PUT".
When I run the code using Visual Studio Code I am seeing the data being populated but once it is deployed using IIS the data is not being populated which leads me to believe it could possibly be some type of security measure within the IIS.
I have thought that the reason may be because it is just a http website currently and the browser is not allowing API requests but I have also tried to make the website https using this article (https://www.educba.com/flask-https/) explaining the adhoc ssl context method
I am able to access the deployment URL and my application shows up but the functionality of the API is still not working so I am thinking that the HTTP and HTTPS is not the root cause.
Here is my app.py currently
from flask import Flask, render_template
app = Flask(__name__)
#app.route("/")
def index():
return render_template('index.html')
#app.route("/active", methods=['GET', 'POST'])
def active():
try:
import json
import requests
import pandas as pd
EXUser = 'API'
EXPw = 'test'
EXI = 'Example'
url = requests.get(
f'https://{EXI}.api.com/api/values/0153ecf7-9e90-4aef-ad0b-ed8d0aa7092f', auth=(EXUser,EXPw ))
myToken = 'exampletoken'
myUrl = 'https://api2.com/api/v1/devices.json'
head = {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json',
'Authorization': 'Token Example',
'Cookie': 'XSRF-TOKEN=Example'
}
sfresp = requests.get(myUrl, headers=head)
r = json.loads(url.content)
sr = json.loads(sfresp.content)
df = pd.json_normalize(sr, 'devices')
df1 = pd.json_normalize(r, record_path='userDefinedValues')
df1.set_index('userDefinedKeyId', inplace=True)
owner = df1['value']['66ea21fb-036f-4081-ac58-6b4c9e3caa54']
lon = df['device.location.lng'][0].astype(str)
bat = df['device.battery_status'][0].astype(str)
lat = df['device.location.lat'][0].astype(str)
did = df['device.id'][0].astype(str)
payload = 'properties=[ { "name": "Owner", "value": "' + owner + '" } ]'
myUrl2 = f'https://api2.com/api/v1/devices/{did}/custom_properties.json'
test = requests.put(myUrl2, headers=head, data=payload)
payload2 = {'userDefinedValues': [{'userDefinedKeyId': 'ec94503d-efb4-4b04-82ac-1717beff85b9',
'userDefinedKeyName': 'Latitude',
'value': lat},
{'userDefinedKeyId': 'a64d94c5-e156-4f2d-b218-82597bfa95ce',
'userDefinedKeyName': 'Longitude',
'value': lon},
{'userDefinedKeyId': '537b843a-044e-45c0-9625-93c4f52c539d',
'userDefinedKeyName': 'Battery Level',
'value': bat}]}
test1 = requests.patch(f'https://{EXI}.api.com/api/assets/0153ecf7-9e90-4aef-ad0b-ed8d0aa7092f', auth=(
EXUser, AVIPw), data=json.dumps(payload2))
return render_template('index.html')
except:
return render_template('index.html')
if __name__ == '__main__':
app.run(ssl_context='adhoc')
index.html code of the single start button that renders on the page
<html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#4.3.1/dist/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<title>Data Integration Demo</title>
</head>
<body>
<form action="/active" method="POST">
<input type="submit" value="Start">
</form>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js#1.14.7/dist/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#4.3.1/dist/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>
</html>
Just to reiterate the app is deployed using IIS, I have followed the steps here (https://medium.com/#dpralay07/deploy-a-python-flask-application-in-iis-server-and-run-on-machine-ip-address-ddb81df8edf3). I am able to browse to the application using localhost but the part of the script is not running that PUTs and GETs data. The code works when I run it using Visual Studio Code but once it has been deployed it no longer does the API requests process within the code. I tried to replace values into example values so I may have missed some sections in the code, I can assure that it does work.
I am creating a web application that receives some text, converts the text into speech, and returns an mp3 file, which is saved to a temporary directory.
I want to be able to download the file from the html page (i.e., the frontend), but I don't know how to do that properly.
I know with Flask you can do this:
from app import app
from flask import Flask, send_file, render_template
#app.route('/')
def upload_form():
return render_template('download.html')
#app.route('/download')
def download_file():
path = "html2pdf.pdf"
return send_file(path, as_attachment=True)
if __name__ == "__main__":
app.run()
HTML Example:
<!doctype html>
<title>Python Flask File Download Example</title>
<h2>Download a file</h2>
<p>Download</p>
So how do I replicate this with FastAPI?
FastAPI Code:
from fastapi import FastAPI, File, Request, Response, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, HTMLResponse, StreamingResponse
from fastapi.templating import Jinja2Templates
from gtts import gTTS
templates = Jinja2Templates(directory="templates")
def text_to_speech(language:str, text: str) -> str:
tts = gTTS(text=text, lang=language, slow=False)
tts.save("./temp/welcome.mp3")
#os.system("mpg321 /temp/welcome.mp3")
return "Text to speech conversion successful"
#app.get("/")
def home(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
#app.get("/text2speech")
async def home(request: Request):
if request.method == "POST":
form = await request.form()
if form["message"] and form["language"]:
language = form["language"]
text = form["message"]
translate = text_to_speech(language, text)
path = './temp/welcome.mp3'
value = FileResponse("./temp/welcome.mp3", media_type="audio/mp3")
return value
# return templates.TemplateResponse(
# "index.html",
# {"request": request, "message": text, "language": language, "download": value},
# )
Sample HTML File:
<!doctype html>
<title>Download MP3 File</title>
<h2>Download a file</h2>
<p>Download</p>
Use the Form keyword to define Form-data in your endpoint, and more specifically, use Form(...) to make a parameter required, instead of using await request.form() and manually checking if the user submitted the required parameters. After processing the received data and generating the audio file, you can use FileResponse to return the file to the user. Note: use the headers argument in FileResponse to set the Content-Disposition header using the attachment parameter—as described in this answer—to have the file downloaded to your device. Failing to set the headers, or using the inline parameter isntead, would lead to 405 Method Not Allowed error, as the browser attempts to access the file using a GET request (however, only POST requests are allowed to the /text2speech endpoint). Have a look at Option 1 in the examples below.
If you wanted the /text2speech endpoint supporting both GET and POST requests (as shown in your question), you could either use #app.api_route("/text2speech", methods=["GET", "POST"]) and use request.method to check which one has been called, or define two different endpoints e.g., #app.post('/text2speech') and #app.get('/text2speech'). However, you don't necessarily need to do that in this case. Additionally, you have added a Download hyperlink to your template for the user to download the file. However, you haven't provided any information as to how you expect this to work. This wouldn't work in a scenario where you don't have static files, but dynamically generated audio files (as in your case), as well as multiple users accessing the API at the same time; unless, for example, you generated random UUIDs for the filenames and saved the files in a StaticFiles directory—or added that unique identifier as a query/path parameter (you could also use cookies instead, see here and here) to the URL in order to identify the file to be downloaded—and sent the URL back to the user. In that case, you would need a Javascript interface/library, such as Fetch API, to make an asynchronous HTTP request—as described in this answer—in order to get the URL to the file and display it in the Download hyperlink. Have a look at Option 2 below. Note: The example in Option 2 uses a simple dict to map the filepaths to UUIDs, for demo purposes. In a real-world scenario, where multiple users access the API and several workers might be used, you may consider using a database storage, or Key-Value stores (Caches), as described here and here. You would also need to have a mechanism for deleting the files from the database and disk, once they have been downloaded, as well as make sure that users do not have unauthorised access to other users' audio files.
Option 1
app.py
from fastapi import FastAPI, Request, Form
from fastapi.templating import Jinja2Templates
from fastapi.responses import FileResponse
import os
app = FastAPI()
templates = Jinja2Templates(directory="templates")
#app.get('/')
def main(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
#app.post('/text2speech')
def convert(request: Request, message: str = Form(...), language: str = Form(...)):
# do some processing here
filepath = './temp/welcome.mp3'
filename = os.path.basename(filepath)
headers = {'Content-Disposition': f'attachment; filename="{filename}"'}
return FileResponse(filepath, headers=headers, media_type="audio/mp3")
An alternative to the above would be to read the file data inside your endpoint—or, in case the data were fully loaded into memory beforehand, such as here, here and here—and return a custom Response directly, as shown below:
from fastapi import Response
#app.post('/text2speech')
...
with open(filepath, "rb") as f:
contents = f.read() # file contents could be already fully loaded into RAM
headers = {'Content-Disposition': f'attachment; filename="{filename}"'}
return Response(contents, headers=headers, media_type='audio/mp3')
In case you had to return a file that is too large to fit into memory—e.g., if you have 8GB of RAM, you can’t load a 50GB file—you could use StreamingResponse, which would load the file into memory in chunks and process the data one chunk at a time (If you find yield from f being rather slow, please have a look at this answer for faster alternatives):
from fastapi.responses import StreamingResponse
#app.post('/text2speech')
...
def iterfile():
with open(filepath, "rb") as f:
yield from f
headers = {'Content-Disposition': f'attachment; filename="{filename}"'}
return StreamingResponse(iterfile(), headers=headers, media_type="audio/mp3")
templates/index.html
<!DOCTYPE html>
<html>
<head>
<title>Convert Text to Speech</title>
</head>
<body>
<form method="post" action="http://127.0.0.1:8000/text2speech">
message : <input type="text" name="message" value="This is a sample message"><br>
language : <input type="text" name="language" value="en"><br>
<input type="submit" value="submit">
</form>
</body>
</html>
Using JavaScript to download the file
In case you used a JavaScript interface, such as Fetch API, in the frontend to issue the file-download request—instead of using an HTML <form>, as demonstrated above—please have a look at this answer, as well as this answer and this answer on how to download the file in the frontend through JavaScript.
Option 2
app.py
from fastapi import FastAPI, Request, Form
from fastapi.templating import Jinja2Templates
from fastapi.responses import FileResponse
import uuid
import os
app = FastAPI()
templates = Jinja2Templates(directory="templates")
files = {}
#app.get('/')
def main(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
#app.get('/download')
def download_file(request: Request, fileId: str):
filepath = files.get(fileId)
if filepath:
filename = os.path.basename(filepath)
headers = {'Content-Disposition': f'attachment; filename="{filename}"'}
return FileResponse(filepath, headers=headers, media_type='audio/mp3')
#app.post('/text2speech')
def convert(request: Request, message: str = Form(...), language: str = Form(...)):
# do some processing here
filepath = './temp/welcome.mp3'
file_id = str(uuid.uuid4())
files[file_id] = filepath
file_url = f'/download?fileId={file_id}'
return {"fileURL": file_url}
templates/index.html
<!DOCTYPE html>
<html>
<head>
<title>Convert Text to Speech</title>
</head>
<body>
<form method="post" id="myForm">
message : <input type="text" name="message" value="This is a sample message"><br>
language : <input type="text" name="language" value="en"><br>
<input type="button" value="Submit" onclick="submitForm()">
</form>
<a id="downloadLink" href=""></a>
<script type="text/javascript">
function submitForm() {
var formElement = document.getElementById('myForm');
var data = new FormData(formElement);
fetch('/text2speech', {
method: 'POST',
body: data,
})
.then(response => response.json())
.then(data => {
document.getElementById("downloadLink").href = data.fileURL;
document.getElementById("downloadLink").innerHTML = "Download";
})
.catch(error => {
console.error(error);
});
}
</script>
</body>
</html>
Removing a File after it's been downloaded
For Option 1 above, to remove a file after it has been downloaded by the user, you can simply define a BackgroundTask to be run after returning the response. For example:
from fastapi import BackgroundTasks
import os
#app.post('/text2speech')
def convert(request: Request, background_tasks: BackgroundTasks, ...):
filepath = 'welcome.mp3'
# ...
background_tasks.add_task(os.remove, path=filepath)
return FileResponse(filepath, headers=headers, media_type="audio/mp3")
For Option 2, however, you would have to make sure to delete the key (i.e., file_id) pointing to the given filepath from the cache as well. Hence, you should create a background task function, as shown below:
from fastapi import BackgroundTasks
import os
files = {}
def remove_file(filepath, fileId):
os.remove(filepath)
del files[fileId]
#app.get('/download')
def download_file(request: Request, fileId: str, background_tasks: BackgroundTasks):
filepath = files.get(fileId)
if filepath:
# ...
background_tasks.add_task(remove_file, filepath=filepath, fileId=fileId)
return FileResponse(filepath, headers=headers, media_type='audio/mp3')
More details and examples on background tasks can be found here, as well as here.
Im trying to create simple python code that would communicate with 9kw.eu captcha solving service through their api https://www.9kw.eu/api.html#apisubmit-tab. Basically I'm sending base64 encoded image with some keys:values and response from server should be number like: 58952554, but I'm only getting
<response[200]>
Which should mean that the server got my data, but im not getting anything else.
I'm able to get the right result with simple html form:
<form method="post" action="https://www.9kw.eu/index.cgi" enctype="multipart/form-data">
KEY:<br>
<input name="apikey" value="APIKEY"><br>
ACTION<br>
<input name="action" value="usercaptchaupload"><br>
FILE:<br>
<input name="file-upload-01" value="BASE64IMAGEDATAHERE"><br>
TOOL<br>
<input name="source" value="htmlskript"><br>
ROTATE<br>
<input name="rotate" value="1"><br>
Angle<br>
<input name="angle" value="40"><br>
BASE64
<input name="base64" value="1"><br>
Upload:<br>
<input type="submit" value="Upload and get ID">
</form>
This is the python code, which should do the same thing:
import requests
import time
#base64 image encoding
with open("funcaptcha1.png", "rb") as f:
data = f.read()
filekodovany = data.encode("base64")
#captcha uploader
udajepost = {'apikey':'APIKEY','action':'usercaptchaupload','file-upload-01':filekodovany,'source':'pythonator','rotate':'1','angle':'40','base64':'1'}
headers = {'Content-Type':'multipart/form-data'}
r = requests.post('https://www.9kw.eu/index.cgi', data = udajepost)
print(r)
Thanks for any help.
r = requests.post('https://www.9kw.eu/index.cgi', data = udajepost)
Here, r is the whole response object which has many attributes. I guess, you only need r.text. So, you can just use:
print(r.text)
You're looking for the response of the request:
print(r.text)
In this way you'll have the plain text response.
get json output by:
r.json()
and response_code by:
r.status_code
I'm writing a repoze.who plugin and want to return JSON from the repoze.who authentication middleware and still controll the HTTP status code. How can this be done?
One way to accomplish this, is to implement a repoze.who Challenger interface. The following solution takes advantage of the fact that the WebOb exceptions, in webob.exc, can be used as a WSGI application. The following example shows how this can be used in a hypothetical Facebook plugin, where the 2.x API lets users not grant access to their email, which might be required for a successful registration/authentication:
import json
from webob.acceptparse import MIMEAccept
from webob.exc import HTTPUnauthorized, HTTPBadRequest
FACEBOOK_CONNECT_REPOZE_WHO_NOT_GRANTED = 'repoze.who.facebook_connect.not_granted'
class ExampleJSONChallengerPlugin(object):
json_content_type = 'application/json'
mime_candidates = ['text/html',
'application/xhtml+xml',
json_content_type,
'application/xml',
'text/xml']
def is_json_request_env(self, environ):
"""Checks whether the current request is a json request as deemed by
TurboGears (i.e. response_type is already set) or if the http
accept header favours 'application/json' over html.
"""
if environ['PATH_INFO'].endswith('.json'):
return True
if 'HTTP_ACCEPT' not in environ:
return False
# Try to mimic what Decoration.lookup_template_engine() does.
return MIMEAccept(environ['HTTP_ACCEPT']) \
.best_match(self.mime_candidates) is self.json_content_type
def challenge(self, environ, status, app_headers, forget_headers):
if FACEBOOK_CONNECT_REPOZE_WHO_NOT_GRANTED in environ:
response = HTTPBadRequest(detail={
'not_granted':
environ.pop(FACEBOOK_CONNECT_REPOZE_WHO_NOT_GRANTED),
})
elif status.startswith('401 '):
response = HTTPUnauthorized()
else:
response = None
if response is not None and self.is_json_request_env(environ):
response.body = json.dumps({
'code': response.code,
'status': response.title,
'explanation': response.explanation,
'detail': response.detail,
})
response.content_type = self.json_content_type
return response
A central point here is that response, an instance of a sub-class of webob.exc.WSGIHTTPException, is used as a WSGI application, but also that if response's body attribute is set, then it is not automatically generated, a fact we use to explicitly set the response's body to a JSON-formatted string representation of our dictionary. If the above challenger is invoked during the handling of a request to a URL ending in '.json' or the Accept header includes application/json, the body of the response might render as something like:
{
"status": "Bad Request",
"explanation": "The server could not comply with the request since it is either malformed or otherwise incorrect.",
"code": 400,
"detail": {"not_granted": ["email"]}
}
and if not, then the body will be rendered as HTML:
<html>
<head>
<title>400 Bad Request</title>
</head>
<body>
<h1>400 Bad Request</h1>
The server could not comply with the request since it is either
malformed or otherwise incorrect.<br /><br />
{'not_granted': [u'email']}
</body>
</html>
I have a javascript method that reads the input file submitted via a form and tries to post its data to a flask url, which is supposed to read the posted data and return it with some amendments.
The HTML part:
<form id="form_1">
<input type="file" name="file_1" id="file_1">
<input type="submit" value="submit" id="regan">
</form>
The JS part:
$("#form_1").submit(function(e){
e.preventDefault(e);
var reader = new FileReader();
var line_data = $('#file_1').get(0);
if (line_data.files.length) {
var input_file = line_data.files[0];
reader.readAsText(input_file);
$(reader).on('load', function(e){
data_line = e.target.result;
$.ajax({
url:'url/validate/',
type:'POST',
data:{akey: data_line},
success:function(returned_data){console.log(returned_data);},
error:function(){console.log('sorry...');}
});
});
}
});
The Flask/Python part:
#app.route('/validate_line/')
def validate_line_data():
try:
data = request.form['akey']
except:
data = 'bad'
data = str(data)
return data+'was_received'
So far, its reading the submitted text file successfully in javascript, but it seems like its not posting via the ajax post method and giving an error url/validate/ 405 (METHOD NOT ALLOWED).
Any help would be great. Thanks.
For Flask to accept POST requests, you need to specify so in the decorator:
#app.route('/validate_line/', methods=['POST'])
If you want it to accept GET requests as well, change to
['GET', 'POST']