Send HTTP response that doesn't change the user's current page - python

I have a JavaScript bookmarklet that POSTs information to a (Flask powered) server while the user is on some other page (i.e. not one on my server). I don't want to interrupt the user's browsing by hijacking their session with my server response.
My initial thought was that I could suppress the HTTP response from Flask somehow; prevent it from sending anything to the client so they aren't mysteriously redirected. I was hoping I could do this by perhaps having a null return from a view.
I then thought that might be some HTTP response that lets the client know the information was successfully submitted, but will leave the client on their current page. Suppose a header value like "Here is the result of your request, but you should not alter your current display"?

To answer your amended question, yes there is such a response. From RFC 2616-section 10 (emphasis added):
10.2.5 204 No Content
The server has fulfilled the request but does not need to return an
entity-body, and might want to return updated metainformation. The
response MAY include new or updated metainformation in the form of
entity-headers, which if present SHOULD be associated with the
requested variant.
If the client is a user agent, it SHOULD NOT change its document view
from that which caused the request to be sent. This response is
primarily intended to allow input for actions to take place without
causing a change to the user agent's active document view, although
any new or updated metainformation SHOULD be applied to the document
currently in the user agent's active view.
The 204 response MUST NOT include a message-body, and thus is always
terminated by the first empty line after the header fields.
Thus from flask you can do something like this. Remember, the response must not include a message body, so any data you want to send back should be put into a cookie.
#app.route('/')
def index():
r = flask.Response()
r.set_cookie("My important cookie", value=some_cool_value)
return r, 204

No, it is not possible. Flask is built on Werkzeug, which implements the WSGI spec. The WSGI cycle requires sending a response to each request. Droping the response would require control over the TCP/IP connection at a far lower level even that HTTP. This is outside the domain of WSGI, therefore outside the domain of Flask.
You could return an error code, or an empty body, but you have to return something.
return '' # empty body

Related

FastAPI's RedirectResponse doesn't work as expected in Swagger UI

I have a FastAPI app with a download endpoint. What this download endpoint does is to use a BlobServiceClient (for Azure Blob Storage) to generate a token and a Blob URL to a file specified in the request. What I want to do is to redirect the user to that URL. Here is a code snippet of the download enpoint (I commented some things out because I'm not allowed to show the code).
#router.get("..path", tags=["some tags"], summary=..., responses={404: {"model": ...}, 403: {"model": ...}, 307: {"model": ...}}, response_model_exclude_none=True)
async def download_file(
# there's a depends on an API key
blob_path: str = Query(
...
)):
credential = ClientSecretCredential(...) //secrets
blob_service_client = BlobServiceClient(f"https://{storage_account}.blob.core.windows.net", credential=credential)
user_delegation_key = blob_service_client.get_user_delegation_key(key_start_time=datetime.utcnow(),key_expiry_time=datetime.utcnow() + timedelta(minutes=30))
token = generate_blob_sas(account_name=...,
container_name=...,
blob_name=blob_path,
user_delegation_key=user_delegation_key,
permission=BlobSasPermissions(read=True),
expiry=datetime.utcnow() + timedelta(minutes=30))
blob_url = f'https://{storage_account}.blob.core.windows.net/{container_name}/{blob_path}?{token}'
print(blob_url)
response = RedirectResponse(blob_url)
return response
What I expected is the query to be executed, and after the response is returned, the download to start in the background or in a separate tab. What I've got instead is a different response as you can see in the Swagger:
I also had a look in the Network tab to see what is happening with that request:
Looks like there is an OPTIONS request and I assume that I'm getting the response to that request. Not sure if this is how Swagger handles the request. Any idea how/why this is happening and how to fix it? Thank you!
To start with, the HTTP OPTIONS, in CORS, is a preflight request that is automatically issued by the browser, before the actual request—is not the one that returns the File response. It requests the permitted communication options for a given server, and the server responds with an Access-Control-Allow-Methods header including a set of permitted methods (e.g., Access-Control-Allow-Methods: OPTIONS, GET, HEAD, POST, DELETE). The preflight response can be optionally cached for the requests created in the same URL using Access-Control-Max-Age header, thus allowing the server to limit the number of preflight requests. The value of this header is expressed in seconds; hence, allowing caching for 10 minutes, for example, would look as Access-Control-Max-Age: 600.
As for the RedirectResponse, Swagger UI always follows redirect responses. In a fetch request, for instance, the redirect parameter would be set to follow, indicating that the redirect should be followed. This means that Swagger UI follows the redirect and waits for the response to be completely received before providing you with a Download file link (as shown in the screenshot you provided above) that would allow you to download the file. That is also why you can't see the download starting either in the background or in a new tab. As mentioned in the linked github post above, it is not possible to change that behaviour, which could allow you to handle it differently, similar to the approach demonstrated in this answer.
Instead of using Swagger UI to test that specific endpoint, you can either test it directly through typing the URL to your API endpoint in the address bar of your browser (since it is a GET endpoint, you can do that, as when you type a URL in the address bar of your browser, it performs a GET request), or create your own custom Template (or use HTMLResponse) and submit an HTML <form>, as shown in this answer.

Redirect to a different URL only changing the domain name in flask

I am using Python flask. I have a POST request with some payload coming on say:
abc.com/hello/hello1
I want to redirect this (302) to:
xyz.com/hello/hello1
only changing the domain name while keeping the remaining part as it is and also the payload. Is there a simple way to do this?
As per RFC, redirect requests (all 3xx) cannot contain request data or headers. You will miss the payload, supplied via POST in original request.
There are two possible workaround I could think of right away:
Give the client new URL, and implement further logic on client side;
Create a proxy handler on backend, which will do a request by itself and give the answer back as it's own.
EDIT: As per Andrejs Cainikovs's comment below, this would not work for a POST with payload.
In your endpoint, get the url that was used using request.url (see request API here for more options). Then you can rewrite it and make a redirect.
newUrl = "xyz.com/" + route
return redirect(newUrl, code=302)

Flask JWT extend validity of token on each request

Scenario
A logged in user will have a token expiry of 24 hours. Within that period, all request with #jwt_required decorator will have the current access token's expiry extended by another 24 hours. There is a maximum validity of 168(24 * 7) hours.
It is possible to use access_token and refresh_token.
ret = {
'access_token': create_access_token(identity=username, fresh=True),
'refresh_token': create_refresh_token(identity=username)
}
But that means every API call from my applicatino will be two requests:
1. Actual HTTP Request
2. Refresh the auth token
#app.route('/refresh', methods=['POST'])
#jwt_refresh_token_required
def refresh():
current_user = get_jwt_identity()
ret = {
'access_token': create_access_token(identity=current_user)
}
return jsonify(ret), 200
Is there a way to implicitly extend an auth token?
EDIT: There is now documentation around this here: https://flask-jwt-extended.readthedocs.io/en/latest/refreshing_tokens/
Author of flask-jwt-extended here. Technically you cannot actually extend a token, you can only replace it with a new JWT that has a new expires time. There are a few ways you could simulate this though.
First, instead of having the client request a new token, you could have the server itself just implicitly send back a new token on every request. You could send the new JWTs back in a header instead of in the JSON payload, so that you wouldn't have to modify you JSON data to account for the possibility of a new JWT. Your clients would need to be aware of this though, they would need to check for that new header on every request and replace their current JWT with the new one if it is present. You could probably use a flask after_request method to do this, so you didn't have to add that functionality to all your endpoints. A similar effect could be achieved when storing the JWTs in cookies, with the differences being that cookies are automatically stored in your browser (so your client wouldn't have to manually look for them on every request), and with the added complexity of CSRF protection if you go this route (http://flask-jwt-extended.readthedocs.io/en/latest/tokens_in_cookies.html).
The above should work fine, but you will be creating a lot of access tokens that are thrown away right after being created, which probably isn't ideal. A variation of the above is to check if the token is near expiring (maybe if it is more then half way to being expired) and only create and return a new token if that is the case. Another variation of this would be to have the client check if the token is about to expire (via javascript) and if it is, use the refresh token to request a new access token. To do that, you would need to split the JWT on dots ('.'), base64 decode the second set of strings from that split (index 1), and grab the 'exp' data from there.
A second way you could do this is actually wait for a token to expire, and then use the refresh token to generate a new access token and remake the request (reactive instead of proactive). That might look like making a request, checking if the http code is 401, if so use the refresh token to generate a new access token, then making the request again.
Hope this helps :)
app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "super-secret" # Change this!
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=30)
jwt = JWTManager(app)
change time according to your requirement

Flask appropriate response for POST without template?

So I have a method for creating new exercises in the database for a workout app. I'm using angular to handle the ajax.
#app.route('/create', methods=['POST'])
def create():
data = request.get_json(request.data)
ex = Exercise(data['exName'], data['exType'])
db.session.add(ex)
db.session.commit()
After successfully committing, what is the best response to send back? I don't really need a success message, just need to let client know that it was successfully created. Thanks!
I think you can send back HTTP 204 which is described by w3.org rfc2616:
10.2.5 204 No Content
The server has fulfilled the request but does not need to
return an entity-body, and might want to return updated
metainformation. The response MAY include new or updated
metainformation in the form of entity-headers, which if
present SHOULD be associated with the requested variant.
I think you could do this in the end of your function
return '', 204
I think you should send 201 status(look here) Not 204 because you've just created new entity. And if you are trying to build good RESTful API you need to send 201 with the object you've created before.
And later, at the angular side, you can do what you want. You'll let users know about created exercise there.

How to send post variables in HttpRedirect or HttpFound in webob in python ?

I want to send some variable in post when i am doing redirecting in my application.
Scenario is like this
def redirect_with_post():
post_variable = "i am a post variable"
return HttpRedirect(location="/confirm?id=23&user=user1")
# Now i want to send
post_variable
I want to send variable in post instead of querystring as a post request while redirecting.
tools and software used:-
python, webob, route
Redirect is performed by the client (your browser). And it only support a single Location header. You can't send POST data through a redirect. See https://en.wikipedia.org/wiki/HTTP_302

Categories

Resources