I have a HTTP Server in Python which is able to take and save files sent via POST request. However, now I am trying to download requested files with GET request. Basically, I want to send a GET request with a filename and I want my server to offer me a file to download. How would I do so?
My code so far:
class S(BaseHTTPRequestHandler):
def _set_headers(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
def _html(self, message):
"""This just generates an HTML document that includes `message`
in the body. Override, or re-write this do do more interesting stuff.
"""
content = f"<html><body><h1>{message}</h1></body></html>"
return content.encode("utf8") # NOTE: must return a bytes object!
def do_GET(self):
self._set_headers()
self.wfile.write(self._html("hi!"))
def do_HEAD(self):
self._set_headers()
def do_POST(self):
form = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={'REQUEST_METHOD':'POST',
'CONTENT_TYPE':self.headers['Content-Type'],
})
filename = form['file'].filename
data = form['file'].file.read()
open(filename, "wb").write(data)
self._set_headers()
self.wfile.write(self._html("hi!"))
Instead of writing custom methods for http response and routing
engines you can use flask a lighwieght framwork that provide routing
engine and all features and security to the app and this also provide
necessary things to manage all kind of http requests
https://www.palletsprojects.com/p/flask/
Please maintain below structure in your root directory
app.py
from flask import Flask
app = Flask(__name__)
main.py
from flask_cors import CORS
import os
from app import app
# allowing cross browser request with cors configuration
CORS(app, supports_credentials=True,methods=['GET', 'HEAD', 'POST', 'OPTIONS', 'PUT', 'PATCH', 'DELETE'])
if __name__ == "__main__":
app.session_type = os.getenv('SESSION_TYPE')
app.run(debug=os.getenv('DEBUG'), host=os.getenv(
'SERVER_HOST'), port=os.getenv('SERVER_PORT'))
then execute the python main.py it will activate all routes enjoyy!!
Related
I'm trying to understand what is the best way a POST request can be redirected to a GET request.
for example -
POST /redirect HTTP/1.1
Host: www.example1.com
url=www.example2.com
and i've created the following flask to help me with that :
from flask import Flask,request, redirect
app = Flask(__name__)
#app.route('/redirect',methods=['POST'])
def redire():
url = request.form['url']
return redirect('https://www.example2.com', code=307)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8888)
the "issue" in my case, is that the request that is being sent to
https://www.example2.com
is also a POST request which is not what i wanted.
Consider that I don't "care" about the body that needs to be sent to the
https://www.example2.com
endpoint, what is the best way to do so without any user intervention (meaning that I'm aiming for an auto redirect).
Note: I've tried to do it via PHP but I can't seem to figure it out.
Apologies if something is not clear.
In order to redirect a POST request to a GET request, you need to use code=303 because it requires the client to use the GET method to retrieve the requested resource.
#app.route('/redirect',methods=['POST'])
def redire():
url = request.form['url']
return redirect('https://www.example2.com', code=303)
the server
from flask import Flask, redirect
app = Flask(__name__)
#app.route('/redirect', methods=['POST'])
def redire():
return redirect('http://127.0.0.1:8888/get')
#app.route('/get', methods=['GET'])
def iam_get():
return {"code": "ok"}
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8888)
the client
import requests
data = requests.get("http://127.0.0.1:8888/get")
print(data.text)
data = requests.post("http://127.0.0.1:8888/redirect")
print(data.text)
the result as follows
{"code":"ok"}
{"code":"ok"}
I have a Flask application that shall provide an endpoint to download a large file. However, instead of providing it from the file system or generating the file on-the-fly, this file has to be downloaded first from another server via HTTP.
Of course, I could perform a GET request to the external server first, download the file completely and store it in the file system or in memory and then as a second step provide it as a result for the original request. This would look for example like this (also including a basic authentication to indicate why a simple proxy on a lower layer is not sufficient):
#!flask/bin/python
from flask import Flask, jsonify
import os
import requests
from requests.auth import HTTPBasicAuth
app = Flask(__name__)
#app.route('/download')
def download():
auth = HTTPBasicAuth("some_user", "some_password")
session = requests.Session()
session.auth = auth
response = session.get("http://example.com")
return response.content
if __name__ == '__main__':
app.run(host='0.0.0.0', port=1234, debug=True)
However, this increases both the latency and the storage requirements of the application. And also, even if the receiver only requires to perform a partial download (i.e. it performs a HTTP range request) of the file, it has to be fetched from the external server completely, first.
Is there a more elegant option to solve this, i.e. to provide support for HTTP range requests that are directly forwarded to the external server?
According to Proxying to another web service with Flask, Download large file in python with requests and Flask large file download I managed to make a Flask HTTP proxy in stream mode.
from flask import Flask, request, Response
import requests
PROXY_URL = 'http://ipv4.download.thinkbroadband.com/'
def download_file(streamable):
with streamable as stream:
stream.raise_for_status()
for chunk in stream.iter_content(chunk_size=8192):
yield chunk
def _proxy(*args, **kwargs):
resp = requests.request(
method=request.method,
url=request.url.replace(request.host_url, PROXY_URL),
headers={key: value for (key, value) in request.headers if key != 'Host'},
data=request.get_data(),
cookies=request.cookies,
allow_redirects=False,
stream=True)
excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
headers = [(name, value) for (name, value) in resp.raw.headers.items()
if name.lower() not in excluded_headers]
return Response(download_file(resp), resp.status_code, headers)
app = Flask(__name__)
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def download(path):
return _proxy()
if __name__ == '__main__':
app.run(host='0.0.0.0', port=1234, debug=True)
download_file() will open the request in stream mode and yield every chunk as soon as they got streamed.
_proxy() create the request then just create and return a Flask Response using the iterator download_file() as content.
I tested it with https://www.thinkbroadband.com/download where several archive files are free to download for test purpose. (be careful, archives are corrupted, so you better use checksum to make sure you got the expected file).
Some examples:
curl 'http://0.0.0.0:1234/100MB.zip' --output /tmp/100MB.zip
curl 'http://0.0.0.0:1234/20MB.zip' --output /tmp/20MB.zip
I also performed some other tests on random websites to get large images. So far I got no issues.
the api should include one function called "write text to file" and inputs a string parameter
as for the function to write to the disk I have no problem and I implemented the code my problem is how to set the rest API using python.
EDIT:
this is my code:
from flask import (
Flask,
render_template
)
import SocketServer
import SimpleHTTPServer
import re
app = Flask(__name__, template_folder="templates")
#app.route('/index', methods=['GET'])
def index():
return 'Welcome'
#app.route('/write_text_to_file', methods=['POST'])
def write_text_to_file():
f = open("str.txt", "w+")
f.write("hello world")
f.close()
if __name__ == '__main__':
app.run(debug=True)
anyhow when I try to test my rest api:
http://127.0.0.1:5000/write_text_to_file
I am getting the following error:
Now I'm trying to test my rest-api , however how can I make my code to start the server and to the test the post request api, this is my test_class:
import requests
import unittest
API_ENDPOINT="http://127.0.0.1:5000/write_text_to_file"
class test_my_rest_api(unittest.TestCase):
def test_post_request(self):
"""start the server"""
r = requests.post(API_ENDPOINT)
res = r.text
print(res)
also when runnning my request using postman I am getting internal_server_error:
You're doing a GET request for this url, but you've specified that this endpoint can only accept POST:
#app.route('/write_text_to_file', methods=['POST'])
Also, the SocketServer and SimpleHTTPServer imports are not needed with Flask.
The method is not allowed because Chrome (or any browser) makes GET requests.
Whereas, you defined it as POST
#app.route('/write_text_to_file', methods=['POST'])
Either change it to a GET method, or use a tool such as POSTMan to perform other HTTP call types
I am new to API, and get a tasks of creating POST API. I have created a code somehow.
I want to add data to the hello.txt through post API, So how will I do it?
Here is my code:
import flask
from flask import request, jsonify
app = flask.Flask(__name__)
app.config["DEBUG"] = True
#app.route('/api/v1/resources/messages', methods = ['POST'])
def api_message():
if request.headers['Content-Type'] == 'text/plain':
return "Text Message: " + request.data
elif request.headers['Content-Type'] == 'application/octet-stream':
return "Binary message written!"
elif request.headers['Content-Type'] == 'application/json':
f = open('F:\Asif_Ahmed\Projects\api\hello.txt',"w")
f.write(request.data)
f.close()
return "JSON Message: " + json.dumps(request.json)
else:
return "415 Unsupported Media Type ;)"
app.run()
from flask import Flask, jsonify, render_template, request #import flask library
from flask_basicauth import BasicAuth # import flask library for create basic authentication if needed
from flask_cors import CORS # import flask library Cross-Origin Resource Sharing that is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin
app = Flask(__name__)
CORS(app) #set-up cors for my app
#if you want use basic authentication you need set-up username and password
app.config['BASIC_AUTH_USERNAME'] = 'admin'
app.config['BASIC_AUTH_PASSWORD'] = 'password'
basic_auth = BasicAuth(app)#set-up username and password for my app but in this case I'm not specifying yet in which API use them
#app.route('/api/v1/resources/add_messages', methods=['POST'])#create my POST api
#basic_auth.required# set-up basic authentication for this API, comment out if not needed
def update_credential ():
json_credential=request.get_json()#get the JSON sent via API
print (json_credential["message"])#get the node "message" of my JSON
###########
#code to write in your file, you need write the json_credential["message"]
###########
return ("ok")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=1024, threaded=True)#start my flask app with local_host IP and specific port, if you don't specify the port it will run in the default port
In this case the JSON Input should be:
{"message":"your text"}
Please let me know if something is not clear, I even try this code on my local and the JSON is passed without problems.....
So you need run your python script and see that the API is running, if you had no JSON to send and was just a simple API that give back information you should have used even Chrome but in this case that you need send some JSON data I would advice you to use Postman.
See screenshot example:
I'm writing a REST client and I need to mock a HTTP server in my tests. What would be the most appropriate library to do that? It would be great if I could create expected HTTP requests and compare them to actual.
Try HTTPretty, a HTTP client mock library for Python helps you focus on the client side.
You can also create a small mock server on your own.
I am using a small web server called Flask.
import flask
app = flask.Flask(__name__)
def callback():
return flask.jsonify(list())
app.add_url_rule("users", view_func=callback)
app.run()
This will spawn a server under http://localhost:5000/users executing the callback function.
I created a gist to provide a working example with shutdown mechanism etc.
https://gist.github.com/eruvanos/f6f62edb368a20aaa880e12976620db8
You can do this without using any external library by just running a temporary HTTP server.
For example mocking a https://api.ipify.org?format=json
"""Unit tests for ipify"""
import http.server
import threading
import unittest
import urllib.request
class MockIpifyHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
"""HTTPServer mock request handler"""
def do_GET(self): # pylint: disable=invalid-name
"""Handle GET requests"""
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(b'{"ip":"1.2.3.45"}')
def log_request(self, code=None, size=None):
"""Don't log anything"""
class UnitTests(unittest.TestCase):
"""Unit tests for urlopen"""
def test_urlopen(self):
"""Test urlopen ipify"""
server = http.server.ThreadingHTTPServer(
("127.0.0.127", 9999), MockIpifyHTTPRequestHandler
)
with server:
server_thread = threading.Thread(target=server.serve_forever)
server_thread.daemon = True
server_thread.start()
request = request = urllib.request.Request("http://127.0.0.127:9999/")
with urllib.request.urlopen(request) as response:
result = response.read()
server.shutdown()
self.assertEqual(result, b'{"ip":"1.2.3.45"}')
Alternative solution I found is in https://stackoverflow.com/a/34929900/15862
Mockintosh seems like another option.