What causes browsers to open "Save As" window when downloading dynamic content? - html

I am experimenting with an idea to stream dynamic data from a web server into a file on the client device. To implement this idea, I am making use of the HTTP Content-Disposition response header and the HTML download attribute. The following is my sample code, where the server is implemented in Go:
HTML:
<a href="download" download>Download</a>
Server:
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
// Handle download request.
http.HandleFunc("/download", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Disposition", "attachment; filename=\"log.txt\"")
w.Write([]byte("first message\n"))
time.Sleep(10 * time.Second)
// The following for-loop takes about 30 seconds to run on my dev machine.
for i := 0; i < 1000000; i++ {
timestamp := time.Now().Unix()
log(timestamp)
w.Write([]byte(fmt.Sprintf("%v\n", timestamp)))
time.Sleep(time.Microsecond)
}
log("done")
})
// Start HTTP server.
if err := http.ListenAndServe(":8080", nil); err != nil {
log(err)
}
}
func log(v ...interface{}) {
fmt.Println(v...)
}
This sample code works in that it successfully downloads all the content from the download handler when clicking on the "Download" link. However, I am observing the following behavior that I am unable to explain:
I have my browser configured to always ask where to save the downloaded files. When running the sample code above, Chrome opens the Save As window only after the 10 second sleep but before the for-loop is complete and in turn the handler function has returned. Why did Chrome not present the Save As window when the "first message" was sent before the 10 second sleep? What is different between the "first message" and the messages being sent in the for-loop that causes the Save As window to only open when the for-loop starts?
Aside: If FileSystemWritableFileStream had greater cross-browser support, I'd use that to stream dynamic server data directly into a file on the client side.

Go's http.ResponseWriter has a default 4KB buffer, defined at the Transport level:
type Transport struct {
// ...
// WriteBufferSize specifies the size of the write buffer used
// when writing to the transport.
// If zero, a default (currently 4KB) is used.
WriteBufferSize int
// ...
}
In some instances, when using standard responses, you can make use the Flush method by using type assertion with the http.Flusher interface to send the bytes right away:
if f, ok := w.(http.Flusher); ok {
f.Flush()
}

To have Firefox open the Save As window immediately after "first message" is sent, Ricardo Souza's answer appears to be all that's needed. To have Chrome do the same, the response's Content-Type header also needs to be set to anything other than the default text/plain (thanks to this SO answer). Example:
w.Header().Set("Content-Type", "application/octet-stream; charset=utf-8")

Related

Minio: how to get right link to display image on html

I need to get images from Minio bucket, but I cannot display that image.
I found out that problem was in link. I cannot open it even with browser. So, here is the problem:
GET https://127.0.0.1:9000/myphotos/Jungles.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=7PAB237ARMGX7RTYHUSL%2F20221202%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221202T133028Z&X-Amz-Expires=604800&X-Amz-Security-Token=eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiI3UEFCMjM3QVJNR1g3UlRZSFVTTCIsImV4cCI6MTY3MDAyNzIyNiwicGFyZW50IjoiS2VtYWxBdGRheWV3In0.okb2wO_iLhOlwWeNbixec4R5MRgGw2_KCY_SB9NfuseUI3g9gzTccycbaA6UnZiuuLzbpxPM5tR_hnxa_Y8zWQ&X-Amz-SignedHeaders=host&versionId=null&X-Amz-Signature=281fab24bbe3d651f89c160f5a613512f5e4503f40300ef0008ac94bd9c8f90b
net::ERR_CONNECTION_REFUSED
My code that has been used to upload that file:
package main
import (
"context"
"log"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
func main() {
ctx := context.Background()
endpoint := "play.minio.io"
accessKeyId := "KemalAtdayew"
secretAccessKey := "K862008971a!"
useSSL := true
// init minio client object
minioClient, err := minio.New(endpoint, &minio.Options{
Creds: credentials.NewStaticV4(accessKeyId, secretAccessKey, ""),
Secure: useSSL,
})
if err != nil {
log.Fatalln(err)
}
// make a new bucket called myphoto
bucketName := "photobucket"
location := "us-east-1"
err = minioClient.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: location})
if err != nil {
// check to see if we already own this bucket
exists, errBucketExists := minioClient.BucketExists(ctx, bucketName)
if errBucketExists == nil && exists {
log.Printf("We already own %s\n", bucketName)
} else {
log.Fatalln(err)
}
} else {
log.Printf("Successfully created %s\n", bucketName)
}
// upload you photos
objectName := "Jungles.jpeg"
filePath := "/minio-1/Jungles.jpeg"
contentType := "image/jpeg"
// upload the zip file FPutObject
info, err := minioClient.FPutObject(ctx, bucketName, objectName, filePath, minio.PutObjectOptions{ContentType: contentType})
if err != nil {
log.Fatalln(err)
}
log.Printf("Successfully uploaded %s of size %d\n", objectName, info.Size)
}
I also gave permission and made it public. Still nothing.
<!DOCTYPE html>
<html>
<head>
<title> Minio </title>
<meta charset="utf-8">
</head>
<body>
<div>
<img src="https://127.0.0.1:9000/myphotos/Jungles.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=7PAB237ARMGX7RTYHUSL%2F20221202%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221202T124101Z&X-Amz-Expires=604800&X-Amz-Security-Token=eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiI3UEFCMjM3QVJNR1g3UlRZSFVTTCIsImV4cCI6MTY3MDAyNzIyNiwicGFyZW50IjoiS2VtYWxBdGRheWV3In0.okb2wO_iLhOlwWeNbixec4R5MRgGw2_KCY_SB9NfuseUI3g9gzTccycbaA6UnZiuuLzbpxPM5tR_hnxa_Y8zWQ&X-Amz-SignedHeaders=host&versionId=null&X-Amz-Signature=5027bd8021a58548ce6be5dead3b622afd951f157a289320ef7dab7701baa7d2" alt="Photo from Minio">
</div>
</body>
</html>
Tried to change html code. Then, found out that it's not html problem.
Tried to share in any other possible way except than, "bucket->click on photo -> click on share"
Link is invalid, but there is no other proper way to get link to that image in bucket.
The path to your local image seems to be strange. Verify if you can open your image manually, and remove all the parameters after the image extension, it should be Forest.jpg
The path to your local image seems to be strange. Verify if you can open your image manually, and remove all the parameters after the image extension, it should be Forest.jpg
If you have Minio running in a container, it is always a mess with 127.0.0.1 or localhost.
Try to generate the link with the minioclient.
mc alias set myminio http://localhost:9000 user password
mc share download myminio/mybucket/object.txt
it will return something like this:
mc share download --recursive minio/testbucket
URL: http://localhost:9000/testbucket/KUBERNETES_AN_ENTERPRISE_GUIDE.pdf
Expire: 7 days 0 hours 0 minutes 0 seconds
Share: http://localhost:9000/testbucket/KUBERNETES_AN_ENTERPRISE_GUIDE.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20221207%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221207T130336Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=8668da57727f04e7c7e8b15f5d8852fa3801e323cfbc6198384737b77f54cb0b
That link you can open in your Browser.
Note --recursive generate links for all the uploaded files in the bucket.
To get one specific use:
mc share download myminio/testbucket/object.txt.
In production mode as you will use full qualified domain names and not 127.0.0.1 or localhost.
Take a look here about the mc command for generating the link.
https://min.io/docs/minio/linux/reference/minio-mc/mc-share-download.html
If you have a backend and according to your programming language you can also generate links through the api.
Here you find a example for javascript:
https://min.io/docs/minio/linux/developers/javascript/API.html#presignedUrl

Simple way to display image file with HTML forms section

I have been playing around with numerous go server snippets trying to figure out how I can display an image file within a HTML file or go HTTP template along with an html forms section. Basically, the biggest problem if I use a go template I cannot show an image along with html and still keep the project size small. It seems the only way to get the template to work is by organizing the code into a "typical go HTML project" which I am trying to avoid.
Is there any easy way (with only a couple files and not creating a "typical go web project file structure") to display HTML with an image inside a go template? I believe the problem below is basically with http handlers. Either I can have a text handler or image handler but not both? I need both so I can have user control from HTML form which image will be displayed.
If anyone can help I would really appreciate it.
R
Joe
--Revised
Sorry about being unclear. I have limited experience with go templates and I have seen many examples where people use go app project file structures that might include directories such as templates, img, etc. These directories are often 10 or more. Then they talk about using routes within apps and other things that I am timid to get into.
I just view what I want to do as much more simple. I have about 70 images. I just want a way where a user can click an html page that displays an image and just provide a number 1,2,3,4 as feedback depending on what image is being displayed.
I imagined that a single go program (1 file) could receive the number and once received change the img on the html page or allow the user to click a next hyperlink or something to bring up the next image and once it is over the program stops.
package main
import (
"fmt"
"html/template"
"log"
"net/http"
//"strings"
func img(w http.ResponseWriter, r *http.Request) {
//http.Handle("/images/", http.StripPrefix("/images/", http.FileServer(http.Dir("images/"))))
fmt.Println("method:", r.Method) //get request method
if r.Method == "GET" {
t, _ := template.ParseFiles("image.gtpl")
t.Execute(w, nil)
} else {
r.ParseForm()
// logic part of log in
fmt.Println("previmage:", r.Form["previmage"])
fmt.Println("nextimage:", r.Form["nextimage"])
}
}
func main() {
//http.HandleFunc("/", sayhelloName) // setting router rule
http.HandleFunc("/login", login)
err := http.ListenAndServe(":9090", nil) // setting listening port
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
<html>
<head>
<title></title>
</head>
<body> //How to Loop Images based on user submit??
<img src="img/question4.png" alt="Cannot load image" style="width: 800px;height: 800px">
<form action="/login" method="post">
Username:<input type="text" name="previmage">
Password:<input type="password" name="nextimage">
<input type="submit" value="Login">
</form>
</body>
</html>
You have your http.Handle call, which registers the handler, inside the handler. That means every time a request comes in, it tries to register the handler again. That's not allowed (hence the error, which says explicitly that you cannot re-register the same route). You should be registering it in the same place you're registering the handler for the template, i.e. in main:
func main() {
http.HandleFunc("/login", login)
// Register handler correctly
// I changed the route to /img/ to match what you're using in your HTML
http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("images/"))))
err := http.ListenAndServe(":9090", nil) // setting listening port
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}

UIMarkupTextPrintFormatter never renders base64 images

Im creating a pdf file out of html content in swift 3.0:
/**
*
*/
func exportHtmlContentToPDF(HTMLContent: String, filePath: String) {
// let webView = UIWebView(frame: CGRect(x: 0, y: 0, width: 694, height: 603));
// webView.loadHTMLString(HTMLContent, baseURL: nil);
let pdfPrinter = PDFPrinter();
let printFormatter = UIMarkupTextPrintFormatter(markupText: HTMLContent);
// let printFormatter = webView.viewPrintFormatter();
pdfPrinter.addPrintFormatter(printFormatter, startingAtPageAt: 0);
let pdfData = self.drawPDFUsingPrintPageRenderer(printPageRenderer: pdfPrinter);
pdfData?.write(toFile: filePath, atomically: true);
}
/**
*
*/
func drawPDFUsingPrintPageRenderer(printPageRenderer: UIPrintPageRenderer) -> NSData! {
let data = NSMutableData();
UIGraphicsBeginPDFContextToData(data, CGRect.zero, nil);
printPageRenderer.prepare(forDrawingPages: NSMakeRange(0, printPageRenderer.numberOfPages));
let bounds = UIGraphicsGetPDFContextBounds();
for i in 0...(printPageRenderer.numberOfPages - 1) {
UIGraphicsBeginPDFPage();
printPageRenderer.drawPage(at: i, in: bounds);
}
UIGraphicsEndPDFContext();
return data;
}
Everything is rendered fine except my base64 encoded images. The HTML content itself in a webview or inside safari or chrome browser is presented correctly and is showing all images correctly. But the images are never rendered into the pdf.
Why are the images not rendered and how can I get them to be rendered?
This happens because WebKit first parses the HTML into a DOM, and renders content on multiple event loop cycles. You therefore need to wait for not just the page DOM to be ready but for the resource loading to be complete. As you also suggest, you need to refactor your code such that the webview gets loaded first, and you only then export its contents.
To determine the correct time to fire the export, you can observe for the state of the DOM document in the web view. There are multiple ways to do this, but the most readable option I find is a port of an answer to a related Objective-C question: in your UIWebViewDelegate implementation, implement webViewDidFinishLoad in the following way to monitor document.readyState:
func webViewDidFinishLoad(_ webView: UIWebView) {
guard let readyState = webView.stringByEvaluatingJavaScript(from: "document.readyState"),
readyState == "complete" else
{
// document not yet parsed, or resources not yet loaded.
return
}
// This is the last webViewDidFinishLoad call --> export.
//
// There is a problem with this method if you have JS code loading more content:
// in that case -webViewDidFinishLoad can get called again still after document.readyState has already been in state 'complete' once or more.
self.exportHtmlContentToPDF(…)
}
I found the solution!
The export to PDF happens before the rendering process is finished. If you put in a very small picture it is showing up in the PDF. If the picture is too big the rendering process takes too much time but the PDF export isnt waiting for the rendering to finish.
So what I did to make it work is the following:
Before I export to PDF I show the Result of the HTML in a WebView. The WebView is rendering everything correctly and now when I press on export to PDF the PDF is showing up correctly with all images inside.
So I guess this is a huge lag that there is no way to tell the PDF Exporter to wait for the rendering process to finish.

Is there an equivalent to nsiChannel in Google Chrome?

I'm porting my Firefox extension to Google Chrome. I make heavy use of nsiChannel to read HTTP headers and such, like so:
//initialize the channel in onStartRequest
onStartRequest: function (req /*, ctx*/) {
var channel = req.QueryInterface(Components.interfaces.nsIChannel);
//...more init stuff here
}
onDataAvailable: function (req, ctx, stream, offset, count) {
//.. store the data from the stream for later processing...
stream_ctx.bstream.setInputStream(stream);
stream_ctx.bytes += stream_ctx.bstream.readBytes(count);
},
Does Google's Chrome browser have the equivalent functions? I've seen a bit of HTTP-listener-style stuff, but so far I haven't seen anything that has all of the features of nsiChannel. Still, Mozilla's docs on accessing the low-level stuff like this are a little better organized than what I've found for Chrome, so I might have just missed it.
EDIT:
I'm using a stream listener, starting with this:
Components.classes["#mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService)
.newChannel(swf_url, null, null)
.asyncOpen(stream_listener, null);
Here swf_url is the URL to a YouTube video.
The stream_listener is implemented to grab all the bytes from the incoming stream like so:
onDataAvailable: function (req, ctx, stream, offset, count) {
stream_ctx.bstream.setInputStream(stream);
stream_ctx.bytes += stream_ctx.bstream.readBytes(count);
},
When I get to onStopRequest I feed the bytes to a parser/decoder. What I don't know is how to replicate the onDataAvailable method to get the bytes into a stream I can feed into my parser.

Arduino html auto-refresh for constant data stream

We're trying to have the web page served by the Arduino update without having to refresh the page. Our current code is below. Right now the page is refreshing as fast as possible (about once a second), but we'd like to have the data update without having to refresh. Is there a way to do this with html?
Thanks for your help!
void loop() {
WiFiClient client = server.available(); // listen for incoming clients
if (client) { // if you get a client,
Serial.println("new client"); // print a message out the serial port
String currentLine = ""; // make a String to hold incoming data from the client
while (client.connected()) { // loop while the client's connected
if (client.available()) { // if there's bytes to read from the client,
char c = client.read(); // read a byte, then
Serial.write(c); // print it out the serial monitor
if (c == '\n') { // if the byte is a newline character
// if the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0) {
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
// and a content-type so the client knows what's coming, then a blank line:
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
voltageReading = analogRead(A0);
//meta-refresh page as fast as possible
client.print("<HEAD>");
client.print("<meta http-equiv=\"refresh\" content=\"0\">");
client.print("<TITLE />Smart 3 Phase Relay TCNJ</title>");
client.print("</head>");
// the content of the HTTP response follows the header:
client.print("Voltage Reading: ");
client.print(voltageReading);
// The HTTP response ends with another blank line:
client.println();
// break out of the while loop:
break;
}
else { // if you got a newline, then clear currentLine:
currentLine = "";
}
}
else if (c != '\r') { // if you got anything else but a carriage return character,
currentLine += c; // add it to the end of the currentLine
}
// Check to see if the client request was "GET /H" or "GET /L":
if (currentLine.endsWith("GET /H")) {
digitalWrite(9, HIGH); // GET /H turns the LED on
}
if (currentLine.endsWith("GET /L")) {
digitalWrite(9, LOW); // GET /L turns the LED off
}
}
}
// close the connection:
client.stop();
Serial.println("client disonnected");
}
}
HTML is connectionless, that means you can't know if the data has changed. It is said that you can only "PULL", and ajax still pull, but it take xml/json data instead of all html.
But as far as i can understnd, you want a "PUSH" technology, with means the server send new data to clients when avaiable; that is possible using "WebSocket", but it need a server rewrite to support the protocol, or you can use a library
A websocket can be used like a normal socket (with some security restriction enforced by browser, principally you can only connecf to the same domain/ip of the HTML server), that means that once a connection is established, you can just print and read it like you do with a Serial!