I am working on a project where I am using Django as the back end. I am working with Django rest framework, and I have an API to download a File.
#detail_route(methods=['GET'], permission_classes=[IsAuthenticated])
def test(self, request, pk=None):
try:
ticket = Ticket.objects.get(id=pk, user=request.user)
file_path = ticket.qrcode_file.path
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
response = HttpResponse(fh.read(), content_type="image/jpeg")
name = "%s %s %s %s.jpg" % (ticket.show.title, datetime.strftime(ticket.show.date_time,
"%H_%M_%p"),
datetime.strftime(ticket.show.date_time, "%d %B %Y"), ticket.id)
response['Content-Disposition'] = "attachment; filename=%s" % name.replace(" ", "_")
return response
return Response({'error': 'Ticket doest not belong to requested user.'}, status=status.HTTP_403_FORBIDDEN)
except Ticket.DoesNotExist as e:
return Response({'error': str(e)}, status=status.HTTP_404_NOT_FOUND)
On the front-end I am using Nuxtjs (ssr for vuejs). This is a little snippet of code, where a user can download the file by clicking a link of target blank.:
<a class="downloadBtn" target="_blank" :href="`${baseURL}/payments/api/tickets/${ticket.id}/download_ticket/`">Download e-ticket</a>
The web app is running on Nuxtjs server (localhost:3000) and Django server is running on localhost:8000, only the API is used to communicate between the Nuxtjs and Django by using the auth token.
When I click the download link it opens up a new tab and make a request from that new tab, where no token is passed with the request. And since, the django view to download the ticket is permission_classes=[IsAuthenticated] I cannot be authenticated as request.user is anonymous.
Is there any other way that I can make it work to download the file by checking whether the requested user is the Ticket's owner?
Because you are using JWT, you should download the file from your frontend after requesting it from your api using some sort of ajax request and the JWT header.
some_api_request_hendler().then(function (response) {
var file = new Blob([response.data], {type: response.headers("Content-Type")});
var link = document.createElement('a');
link.href = window.URL.createObjectURL(pdf);
link.download = "the_new_file_name";
link.click();
});
I'm using this answer as an example. Your API should not be changed. The way you did it is the way to go.
And your link now need to call your new frontend function instead of just <a href.
Edit:
I asked a question like that a few years ago. You might find some help there.
Browser Compatibility
blob, createElement, createObjectURL
Related
I have an API that downloads a file, I have a button on the button I have a click that sends a request to the API for download a file, but it doesn't work request sending successfully but the file is not downloaded, but when I'm adding the URL into the browser the file is successfully downloaded
HTML
<button (click)="exportFile()">Download</button>
TS
exportFile(): void{
this.companiesService.export().subscribe((res) => {
console.log(res);
});
}
Service
export(){
const headers = this.httpOptions.headers.set('Authorization', `Bearer ${this.cookieService.get('access-token')}`);
return this.http.get(`${this.API_URL}/company/export/`,{headers});
}
You need to process the returned blob and save it as a file. Just returning it is not enough. Perhaps this demo can give you more insight how to improve your service. https://stackblitz.com/edit/angular-file-download-progress-qsqsnf?file=src%2Fapp%2Fdownload.ts
i'am asking the about the right way to downlaod a PushStreamContent present in Post Request ,
i already preapred the backend request , something like this
private static HttpClient Client { get; } = new HttpClient();
public HttpResponseMessage Get()
{
var filenamesAndUrls = new Dictionary<string, string>
{
{ 'README.md', 'https://raw.githubusercontent.com/StephenClearyExamples/AsyncDynamicZip/master/README.md' },
{ '.gitignore', 'https://raw.githubusercontent.com/StephenClearyExamples/AsyncDynamicZip/master/.gitignore'},
};
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new PushStreamContent(async (outputStream, httpContext, transportContext) =>
{
using (var zipStream = new ZipOutputStream(outputStream))
{
foreach (var kvp in filenamesAndUrls)
{
zipStream.PutNextEntry(kvp.Key);
using (var stream = await Client.GetStreamAsync(kvp.Value))
await stream.CopyToAsync(zipStream);
}
}
}),
};
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "MyZipfile.zip" };
return result;
}
and in the front part , i used axios to send Post request and with the result i create blob to download it (i modified the backend to support Post)
but the download take much time and i think this a wrong way to use PushStreamContent and i should use EventSource or something like this.
Thank you.
After few days of search , there are two solution :
Change the download request to Get Request instead of Post.
Use Fetch instead of axios http request and with the response send it to streamsaver package , it's really amazing and instantly start the download on the fly.
I agree with houssem about changing it to a get request.
I'm the creator of StreamSaver and occasionally i search for ppl talking about it and help someone in need. I often have to tell ppl that it's better to use the server to save files rather than using StreamSaver. StreamSaver is meant for client generated content (good for stuff like WebTorrent or webcam recording)
Download can only happen when you navigate the resource. That means you can't use ajax (xhr, fetch, axios, etc) to trigger a download
a <a href>, <iframe>, location.href = all works fine, but if you really need it to be a post request, then you can also submit a <form> but you will have to do it with application/multipart or URLEncoded and not with a json request or anything else. the con of this is that you can't use custom request headers like a authentication header (unless you use service worker to add them)
in such cases it is better with cookies that gets sent along every request
I have a unique situation in terms of difficulty.
I need to send HTML to the server, have the server convert the HTML to a PDF, send that PDF back to the client, and then download the PDF using client-side code.
I have to do it this way because I'm using client-side routing, so the only way I can access my endpoint that should perform this action is via a GET Request with Ajax or Fetch from client-side JavaScript. I am aware of res.sendFile(), but that attempts to render the file in the browser - I don't want that - rather, I want to be able to use client-side code to download the file.
Is it possible, then, to send a PDF file from temporary storage on the server down to the client, allowing client-side code to do whatever it wants to the file thereafter - in my case, downloading it?
I don't believe I have to provide any code because this is more of a theoretical question.
My issue stemmed from the fact that I could not just use res.sendFile() or res.download() from Express because the route was not being accessed by the browser URL bar, rather, my application uses client-side routing, and thus I had to make an HTTP GET Request via Fetch or XMLHttpRequest.
The second issue is that I needed to build the PDF file on the server based on an HTML string sent from the client - so again, I need to make a GET Request sending along a request body.
My solution, then, using Fetch, was to make the Get Request from the client:
fetch('/route' , {
method: 'GET',
body: 'My HTML String'
});
On the server, I have my code that converts the HTML string to a PDF, using the HTML-PDF Node module, and then, I convert that file to a Base64 String, setting the MIME Type and appending data:application/pdf;base64,.
app.get('/route', (req, res) => {
// Use req.body to build and save PDF to temp storage (os.tempdir())
// ...
fs.readFile('./myPDF.pdf', (err, data) => {
if (err) res.status(500).send(err);
res.contentType('application/pdf')
.send(`data:application/pdf;base64,${new Buffer.from(data).toString('base64')}`);
});
});
Back on the client, I have my aforementioned Fetch Request, meaning I just need to tack on the promise to get the response:
fetch('/route', {
method: 'POST',
body: 'My HTML String' // Would define object and stringify.
})
.then(res => res.text())
.then(base64String => {
// Now I just need to download the base64String as a PDF.
});
To make the download, I dynamically create an anchor tag, set its href attribute to the Base64 String in the response from the server, give it a title, and then programmatically click it:
const anchorTag = document.createElement('a');
anchorTag.href = base64String;
anchorTag.download = "My PDF File.pdf";
anchorTag.click();
So, all together and on the client:
fetch('/route', {
method: 'POST',
body: 'My HTML String' // Would define object and stringify.
})
.then(res => res.text())
.then(base64String => {
const anchorTag = document.createElement('a');
anchorTag.href = base64String;
anchorTag.download = "My PDF File.pdf";
anchorTag.click();
});
The solution for using an anchor tag to trigger the download came from another StackOverflow answer. It's also important to note that Base64 Encoding is not very efficient. Better solutions exist, but for my purposes, Base64 will work fine.
It is also imperative to note that Base64 Encoding is precisely that - an Encoding Scheme, not, I repeat, not an Encryption Scheme. So if your PDF files contain privileged information, you would likely want to add token authentication to the endpoint and encrypt the file.
I'm crawling a website using the python requests module. A form on this website requires to solve a ReCaptcha. I've managed to recreate this ReCaptcha on a local website with the same site-key. If I solve the ReCaptcha on my local website and get the 'g-captcha-response' key, would I be able to post this key to the original website? If so, would this work or is Google requiring other informations other than the response key?
# I get the response key from my local website
CaptchaKey = response-key
# I post the response key on the original website
request.post(SubmitURL, data={'g-captcha-response': CaptchaKey}
Would this work? If so, how do I check if the request has been successfully posted?
Google captcha key won't be enough. You should consider a method with selenium+requests.
first part:
from selenium import webdriver
driver = webdriver.Chrome("C:\chromedriver.exe")
driver.get('desiredurl')
#somesleep/waittill element/anything to wait till you solve the recaptcha
cookies = driver.get_cookies()
part 2
session = requests.Session()
for cookie in cookies:
session.cookies.set(cookie['name'], cookie['value'])
payload = { 'username' : '',
'password' : '',
'g-captcha-response': CaptchaKey}
login = session.post('DESIREDURL', data = payload)
This method should work
I am trying to build a website where a user can enter text, which will be picked up via javascript, and sent to a python function where it will be posted to twitter. For the time being, the python function is being stored locally, along with the rest of the site. However, my AJAX isn't too great and I'm having a few issues.
I have written AJAX code which sends a POST request to the python function with the tweet, and the response is the entire python script. No connection is made to the socket my script is listening to. Below is the AJAX function and the python script. Any ideas what's going on?
Thanks in advance for any help!
$(function(){
$('#PostTweet').on('click', function(e) {
var tweet = document.getElementById("theTweet").value;
var len = tweet.length;
if(len > 140){
window.alert("Tweet too long. Please remove some characters");
}else{
callPython(tweet);
}
});
});
function callPython(tweet){
window.alert("sending");
$.ajax({
type: "POST",
url: "tweet.py",
data: tweet,
success: function(response){
window.alert(response);
}
})
}
And the Python Script:
from OAuthSettings import settings
import twitter
from socket import *
consumer_key = settings['consumer_key']
consumer_secret = settings['consumer_secret']
access_token_key = settings['access_token_key']
access_token_secret = settings['access_token_secret']
s = socket()
s.bind(('', 9999))
s.listen(4)
(ns, na) = s.accept()
def PostToTwits(data):
try:
api = twitter.Api(
consumer_key = consumer_key,
consumer_secret = consumer_secret,
access_token_key = access_token_key,
access_token_secret = access_token_secret)
api.PostUpdate(data)
makeConnection(s)
except twitter.TwitterError:
print 'Post Unsuccessful. Error Occurred'
def makeConnection(s):
while True:
print "connected with: " + str(na)
try:
data = ns.recv(4096)
print data
PostToTwits(data)
except:
ns.close()
s.close()
break
makeConnection(s)
Your problem is that you are working with pure sockets which know nothing about HTTP protocol. Take a look at Flask or Bottle web micro frameworks to see how to turn python script or function into web endpoint.
you need a webserver so that your can make request via web browser.
you can web framework like flask or django or you can use webpy.
A simple example using webpy from their website
import web
urls = (
'/(.*)', 'hello'
)
app = web.application(urls, globals())
class hello:
def GET(self, name):
if not name:
name = 'World'
return 'Hello, ' + name + '!'
if __name__ == "__main__":
app.run()
then you call url(your python function) from javascript.
You can totally write a simple web server using sockets, and indeed you've done so. But this approach will quickly get tedious for anything beyond a simple exercise.
For example, your code is restricted to handling a single request handler, which goes to the heart of your problem.
The url on the post request is wrong. In your setup there is no notion of a url "tweet.py". That url would actually work if you were also serving the web page where the jquery lives from the same server (but you can't be).
You have to post to "http://localhost:9999" and you can have any path you want after:"http://localhost:9999/foo", "http://localhost:9999/boo". Just make sure you run the python script from the command line first, so the server is listening.
Also the difference between a get and a post request is part of the HTTP protocol which your simple server doesn't know anything about. This mainly means that it doesn't matter what verb you use on the ajax request. Your server listens for all HTTP verb types.
Lastly, I'm not seeing any data being returned to the client. You need to do something like ns.sendall("Some response"). Tutorials for building a simple http server abound and show different ways of sending responses.