B4J Question [server] how to return a file for download

xulihang

Active Member
Licensed User
How to return a file for download?

Like I have a http handler: /download, I can access /download?filename=1.txt to download a file which is not in the static files folder.
 

OliverA

Expert
Licensed User
Your program can access any file on the machine it is running on that it has rights to.
 

xulihang

Active Member
Licensed User
It's not what I am asking. It is a server app and I want to know how to provide a file for users to download without putting it in the static folder.
 

OliverA

Expert
Licensed User
The root (/) of the web server is the www folder. The www folder is by default in the same folder that you place the server jar file. Place a file there (or subfolder(s) within) and it should be accessible from the outside
 

xulihang

Active Member
Licensed User
Right. The www folder is the static file folder and is by default in the File.DirApp folder. But all files in it are accessible. I want to provide a file for download which is only accessible through an HTTP handler.

For example, a user uploads an image to the server. The server will OCR this image and save the result into a txt file and will raise a download action for the browser.

If it is in a static folder, I can use resq.sendredirect to this file. But if it is not, how to do this.
 

OliverA

Expert
Licensed User
If you are using sendredirect, then you are still serving a file that is in/under the www folder. My first answer is then the correct answer. Inside your handler, you can access any file on your machine, as long as you/your server application has access to it. Want to store your files in c:\temp (or /temp)? Do it. You can open that file and write it to the response object. As to
Another problem is that if I use resp.sendredirect to a txt file in jserver, it will open it instead of giving a direct download dialog.
check out https://stackoverflow.com/a/14281064. Let's say the file you want to sent to the client is "test.txt", you would then use
B4X:
resp.ContentType = "text/plain"
resp.SetHeader("Content-disposition", "attachment; filename=text.txt")
and the download dialog should open with a default name of text.txt. If you don't want to give a default name, you just can set the "Content-disposition" header to "attachment".
 

OliverA

Expert
Licensed User
Sample code. Please note that file1.txt must be in c:\temp and file2.png in c:\temp2. If on a non-windows platform, adjust path names in the fileInfo map. This is untested code, so may need some adjusting
B4X:
'Class module
Sub Class_Globals
    'This map is just there to associate a given file with a location. This shows that each file
    'can be located somewhere else and that location has no relationship to the www folder of the jServer
    'application. This can be done any which way. Could just do one separate folder for all files. Do
    'different folders for files with different extensions, etc. The sky's the limit. Could use a text file
    'or database for file locations. Could use id#'s in the "GET" request and map them to file names/locations.
    'And on and on
    Private fileInfo As Map = CreateMap("file1.txt": "c:\temp", "file2.png": "c:\temp2")
   
    Private contentTypes As Map = CreateMap("txt": "plain\text", "png": "image/png")
End Sub

Public Sub Initialize

End Sub

Sub Handle(req As ServletRequest, resp As ServletResponse)
    If req.Method <> "GET" Then
        resp.SendError(500, "method not supported.")
        Return
    End If

    Dim fName As String = req.getParameter("filename")
    'Here we are using the fileInfo map to retrieve necessary file information. This could
    'be anything else as explained above.
    If fileInfo.ContainsKey(fName) Then
        Dim fExtension As String = GetExtension(fName)
        If contentTypes.ContainsKey(fExtension) Then
            resp.ContentType = contentTypes.Get(fExtension)
            'If you don't want to have a default file name in the download dialog, then just do
            'resp.SetHeader("Content-disposition", "attachment")
            resp.SetHeader("Content-disposition", $"attachment; filename=${fName}"$)
            Dim inStream As InputStream = File.OpenInput(fileInfo.Get(fName), fName)
            Dim outStream As OutputStream = resp.OutputStream
            File.Copy2(inStream, outStream)
        Else
            resp.SendError(500, "unsupported content type")
        End If
    Else
        resp.SendError(404, "file not found")
    End If
End Sub

'https://www.b4x.com/android/forum/threads/detect-extension-from-mime-type.58789/#post-415466
Sub GetExtension(strFileName As String) As String
    If strFileName.IndexOf(".") > 0 Then
        Dim parts() As String = Regex.Split("\.", strFileName)
        Return parts(parts.Length - 1)
    Else
        Return Null
    End If
End Sub
 

xulihang

Active Member
Licensed User
B4X:
resp.SetHeader("Content-disposition", $"attachment; filename=${fName}"$)
This works great!

I use the code below to directly give a txt file for download.

B4X:
Dim text As String
text="Line1"&CRLF&&"Line2"
resp.SetHeader("Content-disposition", $"attachment; filename=out.txt"$)
Dim bytes() As Byte
bytes=text.GetBytes("UTF8")
resp.OutputStream.WriteBytes(bytes,0,bytes.Length)
 
Top