How to save uploaded file to sdcard folder , currently it stores to /data/data/cache folder with filename like "NanoHTTPD-some random number".
I am not able to copy it to any folder location in sdcard.
I would like to save the file to a pre-mentioned folder location in sdcard with the same name as the original file name was uploaded from my html page.
I have tried all sort of codes .But file copy fails all the time. 1)Not able to get correct location of temp file. 2)Not getting original filename that the form was posted with
Here is my implementation .
Please help i am stuck.
public class HttpMultimediaServer extends NanoHTTPD {
private static final String TAG = "HttpMultimediaServer";
private FileInputStream fileInputStream;
public HttpMultimediaServer() {
super(12345);
this.setTempFileManagerFactory(new ExampleManagerFactory());
}
@Override
public Response serve(IHTTPSession session) {
Method method = session.getMethod();
String uri = session.getUri();
Log.e("handle", "url>>" + uri);
if (uri.contains(filesOnly)) {
isfilesOnly = true;
uri = "/";
} else
isfilesOnly = false;
uri = uri.replace("%20", " ");
try {
uri=new String (uri.getBytes ("iso-8859-1"), "UTF-8");
} catch (UnsupportedEncodingException e2) {
e2.printStackTrace();
}
File filePathServer = new File(uri);
if (method==Method.POST) {
try {
Map<String, String> hdrs=session.getHeaders();
Map<String, String> params=session.getParms();
Map<String, String> files = new HashMap<String, String>();
session.parseBody(files);
Set<String> keys = files.keySet();
for(String key: keys){
String name = key;
String loaction = files.get(key);
File tempfile = new File(loaction);
String tempFileName = files.get(loaction).toString();
File fileToMove = new File(tempFileName);
// temp file path returned by NanoHTTPD
String p =Environment.getExternalStorageDirectory().getPath();
String newFile = p + "/LICENSE.txt";
File nf = new File(newFile); // I want to move file here
if (fileToMove.canWrite()) {
boolean success = fileToMove.renameTo(nf);
if (success == true) {
// LOG to console
Log.i("FILE_MOVED_TO", newFile);
} else {
Log.e("FILE_MOVE_ERROR", tempFileName);
}
} else {
Log.e("PERMISSION_ERROR_TEMP_FILE", tempFileName);
}
}
uploadstatus = UPLOAD_SUCESS;
return new Response("UPLOAD_SUCESS");
} catch (Exception e) {
e.printStackTrace();
uploadstatus = UPLOAD_FAIL;
return new Response("UPLOAD_FAIL");
}
}
}
public static void copy(File src, File dst) throws IOException {
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst);
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
}
public static void copyFile(File src, File dst) throws IOException
{
FileChannel inChannel = new FileInputStream(src).getChannel();
FileChannel outChannel = new FileOutputStream(dst).getChannel();
try
{
inChannel.transferTo(0, inChannel.size(), outChannel);
}
finally
{
if (inChannel != null)
inChannel.close();
if (outChannel != null)
outChannel.close();
}
}
private Response getFullResponse(String mimeType,String filePath) throws FileNotFoundException {
// cleanupStreams();
fileInputStream = new FileInputStream(filePath);
return new Response(Response.Status.OK, mimeType, fileInputStream);
}
private Response getPartialResponse(String mimeType, String rangeHeader,String filePath) throws IOException {
File file = new File(filePath);
String rangeValue = rangeHeader.trim().substring("bytes=".length());
long fileLength = file.length();
long start, end;
if (rangeValue.startsWith("-")) {
end = fileLength - 1;
start = fileLength - 1
- Long.parseLong(rangeValue.substring("-".length()));
} else {
String[] range = rangeValue.split("-");
start = Long.parseLong(range[0]);
end = range.length > 1 ? Long.parseLong(range[1])
: fileLength - 1;
}
if (end > fileLength - 1) {
end = fileLength - 1;
}
if (start <= end) {
long contentLength = end - start + 1;
// cleanupStreams();
fileInputStream = new FileInputStream(file);
//noinspection ResultOfMethodCallIgnored
fileInputStream.skip(start);
Response response = new Response(Response.Status.PARTIAL_CONTENT, mimeType, fileInputStream);
response.addHeader("Content-Length", contentLength + "");
response.addHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileLength);
response.addHeader("Content-Type", mimeType);
return response;
} else {
return new Response(Response.Status.RANGE_NOT_SATISFIABLE, "text/html", rangeHeader);
}
}
int UPLOAD_SUCESS = 1;
int UPLOAD_FAIL = -1;
int UPLOAD_NO = 0;
int uploadstatus;
boolean isfilesOnly;
String filesOnly = "?filesOnly=1";
ArrayList<CLocalFile> list;
StringBuilder sb;
public void walkdir(File dir) {
File listFile[] = dir.listFiles();
if (listFile != null) {
for (int i = 0; i < listFile.length; i++) {
// checking if it is a directory
if (listFile[i].isDirectory()) {
if (isfilesOnly)
walkdir(listFile[i]);
else {
CLocalFile f = new CLocalFile();
f.setName(listFile[i].getName());
f.setData(listFile[i].getAbsolutePath());
f.setSize("Folder");
list.add(f);
continue;
}
}
// checking the file extension if it is a file
String fileName = listFile[i].getName();
String extension = "";
int e = fileName.lastIndexOf('.');
if (e > 0) {
extension = fileName.substring(e + 1);
}
if (!isfilesOnly
|| CollabUtility.video_pattern.contains(extension
.toLowerCase(Locale.ENGLISH))
|| CollabUtility.document_pattern.contains(extension
.toLowerCase(Locale.ENGLISH))
|| CollabUtility.audio_pattern.contains(extension
.toLowerCase(Locale.ENGLISH))) {
CLocalFile f = new CLocalFile();
f.setName(fileName);
String mb = "Bytes";
double size = listFile[i].length();
if (size > 1024) {
size = size / 1024;
mb = "KB";
}
if (size > 1024) {
size = size / 1024;
mb = "MB";
}
if (size > 1024) {
size = size / 1024;
mb = "GB";
}
size = Math.floor(size * 100 + 0.5) / 100;
f.setSize(size + " " + mb);
f.setData(listFile[i].getAbsolutePath());
list.add(f);
}
}
}
}
void listofMedia(File file) {
list = new ArrayList<CLocalFile>();
walkdir(file);
// now create the html page
String style = "<style>" + "html {background-color:#eeeeee;} "
+ "body { background-color:#FFFFFF; "
+ "font-family:Tahoma,Arial,Helvetica,sans-serif; "
+ "font-size:18x; " + "border:3px " + "groove #006600; "
+ "padding:15px; } " + "</style>";
String script = "<script language='javascript'>"
+ "function clickit(state) {"
+ "if(state==true){document.getElementById('filesonly').checked="
+ "! document.getElementById('filesonly').checked}"
+ "if ( document.getElementById('filesonly').checked == false ){"
+ "var l=window.location.href;" + "l=l.replace('" + filesOnly
+ "', '');" + "window.location=l;" + "}"
+ "else{var l=window.location.href;"
+ "window.location=String.concat(l,'" + filesOnly + "')" + "}"
+ "}</script>";
Log.d("check", script);
sb = new StringBuilder();
sb.append("<html>");
sb.append("<head>");
sb.append("<title>Files from device</title>");
sb.append(style);
// sb.append("<script language='javascript'>"
// + "function clickit() {"
// + "if ( document.getElementById('filesonly').checked == false ){"
// + "var l=window.location.href;" + "l=l.replace('" + filesOnly
// + "', '');" + "window.location=l;" + "}"
// + "else{var l=window.location.href;"
// + "window.location=String.concat(l,'" + filesOnly + "')" + "}"
// + "}</script>");
sb.append(script);
sb.append("</head>");
sb.append("<body alink=\"blue\" vlink=\"blue\">");
Log.d("check", sb.toString());
// if(true)
// return;
// form upload
sb.append("<h3>File Upload:</h3>");
sb.append("Select a file to upload: <br/>");
sb.append("<form action=\"\" method=\"post\" enctype=\"multipart/form-data\">");
sb.append("<input type=\"file\" name=\"uploadfile\" size=\"50\" />");
sb.append("<input type=\"submit\" value=\"Upload File\" />");
sb.append("</form>");
if (uploadstatus == UPLOAD_FAIL)
sb.append("<h3><font color='red'>The upload was failed</font></h3>");
else if (uploadstatus == UPLOAD_SUCESS)
sb.append("<h3><font color='red'>The upload was successfull</font></h3>");
// if files are there or not
if (list != null && list.size() != 0) {
sb.append("<h3>The following files are hosted live from ");
if (!isfilesOnly)
sb.append("<font color='blue'>" + file.getName()
+ "</font> folder of ");
sb.append("the device</h3>");
} else {
sb.append("<h3>Couldn't find any file from <font color='blue'>"
+ file.getName() + "</font> folder of the device</h3>");
}
// checkbox
if (isfilesOnly)
sb.append("<input type=\"checkbox\" onchange='clickit(false);' checked='true' id=\"filesonly\" />"
+ "<asd onclick='clickit(true);' style=\"cursor:default;\">"
+ "Show only relevant Files (Audio, Video and Documents)</asd>");
else
sb.append("<input type=\"checkbox\" onchange='clickit(false);' id=\"filesonly\" />"
+ "<asd onclick='clickit(true);' style=\"cursor:default;\">"
+ "Show only relevant Files (Audio, Video and Documents)</asd>");
// table of files
sb.append("<table cellpadding='5px' align=''>");
// showing path URLs if not only files
if (!isfilesOnly) {
ArrayList<File> href = new ArrayList<File>();
File parent = new File(file.getPath());
while (parent != null) {
href.add(parent);
// pointing to the next parent
parent = parent.getParentFile();
}
sb.append("<tr>");
sb.append("<td colspan=2><b>");
sb.append("<a href='" + file.getParent() + "'>");
sb.append("UP");
sb.append("</a>");
// printing the whole structure
String path = "";
for (int i = href.size() - 2; i >= 0; --i) {
path = href.get(i).getPath();
if (isfilesOnly)
path += filesOnly;
sb.append(" => <a href='" + path + "'>");
sb.append(href.get(i).getName());
sb.append("</a>");
}
sb.append("</b></td>");
sb.append("</tr>");
}
sb.append("<tr>");
sb.append("<td>");
sb.append("<b>File Name</b>");
sb.append("</td>");
sb.append("<td>");
sb.append("<b>Size / Type</b>");
sb.append("</td>");
sb.append("<tr>");
// sorting the list
Collections.sort(list);
// showing the list of files
for (CLocalFile f : list) {
String data = f.getData();
if (isfilesOnly)
data += filesOnly;
sb.append("<tr>");
sb.append("<td>");
sb.append("<a href='" + data + "'>");
sb.append(f.getName());
sb.append("</a>");
sb.append("</td>");
sb.append("<td align=\"right\">");
sb.append(f.getSize());
sb.append("</td>");
sb.append("</tr>");
}
sb.append("</table>");
sb.append("</body>");
sb.append("</html>");
}
private static class ExampleManagerFactory implements TempFileManagerFactory {
@Override
public TempFileManager create() {
return new ExampleManager();
}
}
private static class ExampleManager implements TempFileManager {
private final String tmpdir;
private final List<TempFile> tempFiles;
private ExampleManager() {
tmpdir = System.getProperty("java.io.tmpdir");
// tmpdir = System.getProperty("/sdcard");
tempFiles = new ArrayList<TempFile>();
}
@Override
public TempFile createTempFile() throws Exception {
DefaultTempFile tempFile = new DefaultTempFile(tmpdir);
tempFiles.add(tempFile);
System.out.println("Created tempFile: " + tempFile.getName());
return tempFile;
}
@Override
public void clear() {
if (!tempFiles.isEmpty()) {
System.out.println("Cleaning up:");
}
for (TempFile file : tempFiles) {
try {
System.out.println(" "+file.getName());
file.delete();
} catch (Exception ignored) {}
}
tempFiles.clear();
}
}
}
If you are using NanoHTTPD r.2.1.0, please try these codes:
@Override
public Response serve(IHTTPSession session) {
Map<String, String> headers = session.getHeaders();
Map<String, String> parms = session.getParms();
Method method = session.getMethod();
String uri = session.getUri();
Map<String, String> files = new HashMap<>();
if (Method.POST.equals(method) || Method.PUT.equals(method)) {
try {
session.parseBody(files);
} catch (IOException ioe) {
return getResponse("Internal Error IO Exception: " + ioe.getMessage());
} catch (ResponseException re) {
return new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
}
}
if ("/uploadfile".equalsIgnoreCase(uri)) {
String filename = parms.get("filename");
String tmpFilePath = files.get("filename");
if (null == filename || null == tmpFilePath) {
// Response for invalid parameters
}
File dst = new File(mCurrentDir, filename);
if (dst.exists()) {
// Response for confirm to overwrite
}
File src = new File(tmpFilePath);
try {
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst);
byte[] buf = new byte[65536];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
} catch (IOException ioe) {
// Response for failed
}
// Response for success
}
// Others...
}
Here's my working code:
public Response serve(IHTTPSession session) {
Map<String, String> headers = session.getHeaders();
Map<String, String> parms = session.getParms();
Method method = session.getMethod();
String uri = session.getUri();
Map<String, String> files = new HashMap<>();
if (Method.POST.equals(method) || Method.PUT.equals(method)) {
try {
session.parseBody(files);
} catch (IOException ioe) {
return getResponse("Internal Error IO Exception: " + ioe.getMessage());
} catch (ResponseException re) {
return new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
}
}
uri = uri.trim().replace(File.separatorChar, '/');
if (uri.indexOf('?') >= 0) {
uri = uri.substring(0, uri.indexOf('?'));
}
// Other implementation goes here...
if ("/uploadfiles".equalsIgnoreCase(uri)) {
String filename, tmpFilePath;
File src, dst;
for (Map.Entry entry : parms.entrySet()) {
if (entry.getKey().toString().substring(0, 8).equalsIgnoreCase("filename")) {
filename = entry.getValue().toString();
tmpFilePath = files.get(entry.getKey().toString());
dst = new File(mCurrentDir, filename);
if (dst.exists()) {
return getResponse("Internal Error: File already exist");
}
src = new File(tmpFilePath);
if (! copyFile(src, dst)) {
return getResponse("Internal Error: Uploading failed");
}
}
}
return getResponse("Success");
}
return getResponse("Error 404: File not found");
}
private boolean deleteFile(File target) {
if (target.isDirectory()) {
for (File child : target.listFiles()) {
if (! deleteFile(child)) {
return false;
}
}
}
return target.delete();
}
private boolean copyFile(File source, File target) {
if (source.isDirectory()) {
if (! target.exists()) {
if (! target.mkdir()) {
return false;
}
}
String[] children = source.list();
for (int i = 0; i < source.listFiles().length; i++) {
if (! copyFile(new File(source, children[i]), new File(target, children[i]))) {
return false;
}
}
} else {
try {
InputStream in = new FileInputStream(source);
OutputStream out = new FileOutputStream(target);
byte[] buf = new byte[65536];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
} catch (IOException ioe) {
return false;
}
}
return true;
}
private Response getResponse(String message) {
return createResponse(Response.Status.OK, MIME_PLAINTEXT, message);
}
// Announce that the file server accepts partial content requests
private Response createResponse(Response.Status status, String mimeType, String message) {
Response res = new Response(status, mimeType, message);
res.addHeader("Accept-Ranges", "bytes");
return res;
}
In order to upload multiple files in a single input file like:
<input type="file" name="filename" multiple>
I modify decodeMultipartData() method in NanoHTTPD.java from:
private void decodeMultipartData(String boundary, ByteBuffer fbuf, BufferedReader in, Map<String, String> parms,
Map<String, String> files) throws ResponseException {
try {
int[] bpositions = getBoundaryPositions(fbuf, boundary.getBytes());
int boundarycount = 1;
String mpline = in.readLine();
while (mpline != null) {
if (!mpline.contains(boundary)) {
throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html");
}
boundarycount++;
Map<String, String> item = new HashMap<String, String>();
mpline = in.readLine();
while (mpline != null && mpline.trim().length() > 0) {
int p = mpline.indexOf(':');
if (p != -1) {
item.put(mpline.substring(0, p).trim().toLowerCase(Locale.US), mpline.substring(p + 1).trim());
}
mpline = in.readLine();
}
if (mpline != null) {
String contentDisposition = item.get("content-disposition");
if (contentDisposition == null) {
throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html");
}
StringTokenizer st = new StringTokenizer(contentDisposition, ";");
Map<String, String> disposition = new HashMap<String, String>();
while (st.hasMoreTokens()) {
String token = st.nextToken().trim();
int p = token.indexOf('=');
if (p != -1) {
disposition.put(token.substring(0, p).trim().toLowerCase(Locale.US), token.substring(p + 1).trim());
}
}
String pname = disposition.get("name");
pname = pname.substring(1, pname.length() - 1);
String value = "";
if (item.get("content-type") == null) {
while (mpline != null && !mpline.contains(boundary)) {
mpline = in.readLine();
if (mpline != null) {
int d = mpline.indexOf(boundary);
if (d == -1) {
value += mpline;
} else {
value += mpline.substring(0, d - 2);
}
}
}
} else {
if (boundarycount > bpositions.length) {
throw new ResponseException(Response.Status.INTERNAL_ERROR, "Error processing request");
}
int offset = stripMultipartHeaders(fbuf, bpositions[boundarycount - 2]);
String path = saveTmpFile(fbuf, offset, bpositions[boundarycount - 1] - offset - 4);
files.put(pname, path);
value = disposition.get("filename");
value = value.substring(1, value.length() - 1);
do {
mpline = in.readLine();
} while (mpline != null && !mpline.contains(boundary));
}
parms.put(pname, value);
}
}
} catch (IOException ioe) {
throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe);
}
}
tobe:
private void decodeMultipartData(String boundary, ByteBuffer fbuf, BufferedReader in, Map<String, String> parms,
Map<String, String> files) throws ResponseException {
try {
String pname_0 = "";
String pname_1 = "";
int pcount = 1;
int[] bpositions = getBoundaryPositions(fbuf, boundary.getBytes());
int boundarycount = 1;
String mpline = in.readLine();
while (mpline != null) {
if (!mpline.contains(boundary)) {
throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html");
}
boundarycount++;
Map<String, String> item = new HashMap<String, String>();
mpline = in.readLine();
while (mpline != null && mpline.trim().length() > 0) {
int p = mpline.indexOf(':');
if (p != -1) {
item.put(mpline.substring(0, p).trim().toLowerCase(Locale.US), mpline.substring(p + 1).trim());
}
mpline = in.readLine();
}
if (mpline != null) {
String contentDisposition = item.get("content-disposition");
if (contentDisposition == null) {
throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html");
}
StringTokenizer st = new StringTokenizer(contentDisposition, ";");
Map<String, String> disposition = new HashMap<String, String>();
while (st.hasMoreTokens()) {
String token = st.nextToken().trim();
int p = token.indexOf('=');
if (p != -1) {
disposition.put(token.substring(0, p).trim().toLowerCase(Locale.US), token.substring(p + 1).trim());
}
}
String pname = disposition.get("name");
pname = pname.substring(1, pname.length() - 1);
if (pname.contentEquals(pname_0)) {
pname_1 = pname + String.valueOf(pcount);
pcount++;
} else {
pname_0 = pname;
pname_1 = pname;
}
String value = "";
if (item.get("content-type") == null) {
while (mpline != null && !mpline.contains(boundary)) {
mpline = in.readLine();
if (mpline != null) {
int d = mpline.indexOf(boundary);
if (d == -1) {
value += mpline;
} else {
value += mpline.substring(0, d - 2);
}
}
}
} else {
if (boundarycount > bpositions.length) {
throw new ResponseException(Response.Status.INTERNAL_ERROR, "Error processing request");
}
int offset = stripMultipartHeaders(fbuf, bpositions[boundarycount - 2]);
String path = saveTmpFile(fbuf, offset, bpositions[boundarycount - 1] - offset - 4);
files.put(pname_1, path);
value = disposition.get("filename");
value = value.substring(1, value.length() - 1);
do {
mpline = in.readLine();
} while (mpline != null && !mpline.contains(boundary));
}
parms.put(pname_1, value);
}
}
} catch (IOException ioe) {
throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe);
}
}
Hope this help and sorry for my bad English..:-)
To allow multiple file upload:
<input type="file" name="filename" multiple>
The same issue existed in the 2.2.1 branch. Following the same logic, I fixed the same function with a few lines of code change.
Add a counter pcount at the beginning of the function:
private void decodeMultipartFormData(String boundary, String encoding, ByteBuffer fbuf, Map<String, String> parms, Map<String, String> files) throws ResponseException {
int pcount = 1;
try {
Then use the counter to update the keyname if filename is not empty:
while (matcher.find()) {
String key = matcher.group(1);
if ("name".equalsIgnoreCase(key)) {
part_name = matcher.group(2);
} else if ("filename".equalsIgnoreCase(key)) {
file_name = matcher.group(2);
// add these two line to support multiple
// files uploaded using the same field Id
if (!file_name.isEmpty()) {
if (pcount > 0)
part_name = part_name + String.valueOf(pcount++);
else
pcount++;
}
}
}
Maybe late, but just for latecommers just like me.
Explained before, the client use okhttp upload a file just like the follow code:
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
//sourceFile is a File as you know
.addFormDataPart("image_file_1", "logo-square1.png", RequestBody.create(MediaType.parse("image/png"), sourceFile))
.build();
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
Response response = client.newCall(request).execute();
The follow code is what you want
@Override
public Response serve(IHTTPSession session) {
Method method = session.getMethod();
// ▼ 1、parse post body ▼
Map<String, String> files = new HashMap<>();
if (Method.POST.equals(method) || Method.PUT.equals(method)) {
try {
session.parseBody(files);
} catch (IOException ioe) {
return getResponse("Internal Error IO Exception: " + ioe.getMessage());
} catch (ResponseException re) {
return newFixedLengthResponse(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
}
}
//after the body parsed, by default nanoHTTPD will save the file to cache and put it into params( "image_file_1" as key and the value is "logo-square1.png");
//files key is just like "image_file_1", and the value is nanoHTTPD's template file path in cache
// ▲ 1、parse post body ▲
// ▼ 2、copy file to target path xiaoyee ▼
Map<String, String> params = session.getParms();
for (Map.Entry<String, String> entry : params.entrySet()) {
final String paramsKey = entry.getKey();
if (paramsKey.contains("image_file_1")) {
final String tmpFilePath = files.get(paramsKey);
final String fileName = paramsKey;
final File tmpFile = new File(tmpFilePath);
final File targetFile = new File(mCurrentDir + fileName);
LogUtil.log("copy file now, source file path: %s,target file path:%s", tmpFile.getAbsoluteFile(), targetFile.getAbsoluteFile());
//a copy file method just what you like
copyFile(tmpFile, targetFile);
//maybe you should put the follow code out
return getResponse("Success");
}
}
// ▲ 2、copy file to target path xiaoyee ▲
return getResponse("Error 404: File not found");
}
来源:https://stackoverflow.com/questions/28739744/nanohttpd-how-to-save-uploaded-file-to-sdcard-folder