File Uploads
SmartFoxServer 3 can handle HTTP/HTTPS file uploads in a secure and efficient way, integrating with your server side Extension code.
Overview
An upload request is handled via the embedded web server (Tomcat) which stores the received file in a temporary directory. From there a server side event, called FILE_UPLOAD is sent to the Zone Extension. Inside the Extension code the developer can move the file from the temp folder to the appropriate application directory, applying validation, processing the files, etc.
The diagram below illustrates the process we have just described:

Each uploaded file is stored under server/data/uploads/ with a temporary file name to avoid collisions. From this position the files can then be moved in the application folders and organized according to the application requirements.
The FILE_UPLOAD server side event provides the following parameters:
| Param Name | Param Type | Description |
|---|---|---|
| USER | User | The sender of the uploaded file(s) |
| UPLOAD_FILE_LIST | List<UploadedFile> |
List of uploaded files containing the original file name and the temp file name |
| UPLOAD_HTTP_PARAMS | Map<String, String> |
A map of custom parameter names passed in the HTTP POST request |
HTTP for testing only
We will often refer to HTTP, rather than HTTPS, for convenience and testing purposes, however it is strongly recommended that any HTTP GET/POST request (upload or other) sent to SmartFoxServer is done using HTTPS.
Basic HTTP Request
Let's see how this work in practice with a basic HTML form.
It is important to note that SmartFoxServer only accepts upload requests coming from logged in Users. A generic HTTP upload request coming from an anonymous web client will be refused.
In order for the following example to work correctly we assume that the form is submitted after the client is already logged in the system, using the HTML/JS API.
<form action="http://localhost:8088/WebServices/SFSFileUpload?sessHashId=01234567890abcdef" method="post" enctype="multipart/form-data">
<strong>Select one or more files to upload:</strong>
<input type="file" multiple name="fileName">
<input type="hidden" name="sessHashId" value="01234567890abcdef" id="sessHashId">
<input type="submit" value="Upload Files">
</form>
The proper form action HTTP url is obtained by reading the SmartFox.httpUploadURI after a successful login. It contains the sessHashId parameter with the current session identifier of the logged in User.
Custom parameters
From the same upload form we can also send custom parameters that can be processed by the Extension running in Zone. This option can be useful to provide extra data accompanying the upload.
Each custom parameter name must begin with a double underscore ( __ ). Here's the previous form with some additional custom fields:
<form action="http://localhost:8088/WebServices/SFSFileUpload?sessHashId=01234567890abcdef" method="post" enctype="multipart/form-data">
<strong>Select one or more files to upload:</strong>
<input type="file" multiple name="fileName">
<input type="hidden" name="__user" value="lapo" id="__name">
<input type="hidden" name="__type" value="picture" id="__type">
<input type="submit" value="Upload Files">
</form>
Uploading from code
Let's now take a look at an example of HTTP upload from code. Note that in the examples below we no longer need to build the upload URI ourselves. We can use the SmartFox.HttpUploadURI property.
NOTE: we have already mentioned that uploads can only be sent after a successful login. We take for granted that you are already familiar with how this works. If not please refer to the relative documentation.
C# (.Net / Godot)
Using .Net HttpClient class we can easily upload any file to SFS.
async Task UploadFileAsync(string filePath, string uploadURL)
{
using var client = new HttpClient();
using var content = new MultipartFormDataContent();
try
{
var fileContent = new ByteArrayContent(File.ReadAllBytes(filePath));
var fileName = Path.GetFileName(filePath);
// Explicitly set Content-Disposition with quoted filename
// Without explicit quoting, Tomcat's server-side parser might misinterpret the filename, leading to the character truncation
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
Name = "\"file\"",
FileName = $"\"{Path.GetFileName(filePath)}\""
};
content.Add(fileContent, "file", fileName);
// Send request
var response = await client.PostAsync(uploadURL, content);
// Check response
if (response.IsSuccessStatusCode)
Console.WriteLine("File uploaded successfully!");
else
Console.WriteLine("Upload failed: " + response.StatusCode);
}
catch (Exception ex)
{
Console.WriteLine("Upload error: " + ex.Message);
}
}
UploadFileAsync("/path/to/upload/file.ext", sfs.HttpUploadURI);
In order to send additional parameters with the request, simply add them to the MultipartFormDataContent instance like this:
C# (Unity)
In Unity it's best to stick with the classes provided by the engine itself instead of using .Net objects. Here's an example of how to upload using WWWForm and UnityWebRequest classes.
IEnumerator UploadFileCoroutine(string filePath, string uploadURL)
{
// Read file as byte array
byte[] fileData = File.ReadAllBytes(filePath);
// Create form data
WWWForm form = new();
form.AddBinaryData("file", fileData, Path.GetFileName(filePath));
// Send upload request
using UnityWebRequest request = UnityWebRequest.Post(uploadURL, form);
yield return request.SendWebRequest();
// Check for errors
if (request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.ProtocolError)
Debug.LogError("Upload failed: " + request.error);
else
Debug.Log("File uploaded successfully!");
}
void UploadFile(string filePath, string uploadURL)
{
StartCoroutine(UploadFileCoroutine(filePath, uploadURL));
}
// Upload file
UploadFile("/path/to/upload/file.ext", smartFox.HttpUploadURI);
If you need extra parameters to be sent along with the upload, you can add fields to the WWWForm instance like this:
Java
In Java we still recommend Apache's HttpClient library even if the JDK nowadays provides its own HttpClient module. The following example is based on the Apache solution which is also bundled in SmartFoxServer 3.
private void testFileUpload()
{
// Select a file to upload
File uploadFile = new File("/path/to/upload/file.zip");
// Prepare the upload encoding
MultipartEntity entity = new MultipartEntity();
entity.addPart("file", new FileBody(uploadFile));
HttpPost request = new HttpPost(smartFox.getHttpUploadURI());
request.setEntity(entity);
HttpClient client = new DefaultHttpClient();
try
{
HttpResponse response = client.execute(request);
}
catch (IOException e)
{
e.printStackTrace();
}
}
HTML5 / JavaScript
In HTML we can use different modalities for uploading files: the regular HTML form-based option, an Ajax-based approach or the modern, promise-based Fetch API. We have already seen how to use the form-based approach in the opening of this tutorial. The Ajax approach involves adding one or more third-party libraries to your project in order to be able to upload files, which is outside the scope of this article. So let's go with the latter.
async function uploadFile(file, uploadURL)
{
const formData = new FormData();
// Append file
formData.append('file', file);
try
{
const response = await fetch(uploadURL, {
method: 'POST',
body: formData
});
// Check response
if (response.ok)
console.log('File uploaded successfully!');
else
console.error('Upload failed');
}
catch (error)
{
console.error('Upload error:', error);
}
}
document.getElementById('fileInput').addEventListener('change', (event) => {
const file = event.target.files[0];
uploadFile(file, smartFox.httpUploadURI);
});
In order to send additional parameters with the request, add them to the FormData instance like this:
Handling uploads on the server side
Let's see how and upload event is handled on the Extension side.
public class UploadTestExtension extends SFSExtension
{
@Override
public void init()
{
addEventHandler(SFSEventType.FILE_UPLOAD, this::onFileUpload);
}
private void onFileUpload(ISFSEvent event)
{
User sender = (User) event.getParameter(SFSEventParam.USER);
List<UploadedFile> upFiles = (List<UploadedFile>) event.getParameter(SFSEventParam.UPLOAD_FILE_LIST);
Map<String, String> httpParams = (Map<String, String>) event.getParameter(SFSEventParam.UPLOAD_HTTP_PARAMS);
String userName = httpParams.get("__user");
String fileType = httpParams.get("__type");
// ...upload logic...
}
}
The event provides us with the sender, a list of uploaded files and the optional extra parameters.
Keep in mind:
The SFSEvent.FILE_UPLOAD event is only dispatched to Extensions at Zone Level
Moving and deleting files
We have mentioned that the uploaded files are temporarily parked in server/data/upload/ with a random name, to avoid name collisions. At this point it's the developer responsibility to verify that the files are valid, within the expected size range, etc. before moving them to the designated application folder.
If during the validation process on or more files don't seem to comply with the allowed formats or size limit, these files should be removed from the upload/ folder to avoid accumulation and saving HD space.
In order to either move or delete the uploaded files we will recommend the JDK's Files.delete() and Files.move() from the java.nio.files package.
private void onFileUpload(ISFSEvent event)
{
User sender = (User) event.getParameter(SFSEventParam.USER);
var upFiles = (List<UploadedFile>) event.getParameter(SFSEventParam.UPLOAD_FILE_LIST);
// Loop through uploaded files
for (UploadedFile uf : upFiles)
{
var tempFile = Paths.get(uf.getTempFilePath());
if (isValidFile(tempFile))
{
// Valid file, move it to the destination folder
var targetFile = Paths.get("path/to/upload-folder/", uf.getOriginalName());
try
{
Files.move(tempFile, targetFile);
}
catch(IOException ioe)
{
ioe.printStackTrace();
}
}
else
{
// Invalid file, remove from temp storage
try
{
Files.delete(tempFile);
}
catch(IOException ioe)
{
ioe.printStackTrace();
}
}
}
}
// Apply file validation criteria here
private boolean isValidFile(Path file)
{
...
}
NOTE: when moving files the Java API will check that the source file isn't already present in the destination folder. If so it will throw an exception. If you want to be able to silently overwrite existing files in your destination folder use this move() call instead:
Downloading the uploaded files
In order to make the uploaded files accessibile from outside, you will need to store them under the server/www/ROOT/your-custom-folder/, where www/ROOT/ represents the root (/) of the Tomcat server.
For example if you upload a file called kermit.png and move it under server/www/ROOT/images it can be downloaded from: http://server-domain:8088/images/kermit.png
You are free to create any folder structure under www/ROOT/ to organize files that can be publicly accessed by clients. On the other hand if uploaded files need to be isolated from the outside world, you should store them elsewhere.
Security aspects
The ability to upload files is turned off by default but can be activated on a per-Zone basis from the AdminTool > ZoneSettings by toggling the FileUpload option.
SmartFoxServer 3 also provides further ways to limit abuses. Under the AdminTool > Server Settings module you will find the following settings:

-
Upload Cooldown Seconds: determines how much time must pass between each upload, every attempt to upload before the cooldown has expired will trigger a refusal
-
Max uploads per User: limits the number of uploads in a single session. Any excess attempt will trigger a refusal
-
Max refused uploads: indicates how many refused upload attempts can be "earned" by a User before getting auto-kicked out
Finally there is also an upload limit of 100MB per uploaded file, meaning that a single upload (be it a single file or multiple) cannot exceed such size. For larger uploads the files will have to be split in chunks.