本文参考:A Guide to NanoHTTPD

NanoHTTPD has a separate dependency for file uploads, so let's add it to our project:

<dependency>
    <groupId>org.nanohttpd</groupId>
    <artifactId>nanohttpd-apache-fileupload</artifactId>
    <version>2.3.1</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

Please note that the servlet-api dependency is also needed (otherwise we'll get a compilation error).

What NanoHTTPD exposes is a class called NanoFileUpload:

@Override
public Response serve(IHTTPSession session) {
    try {
        List<FileItem> files
          = new NanoFileUpload(new DiskFileItemFactory()).parseRequest(session);
        int uploadedCount = 0;
        for (FileItem file : files) {
            try {
                String fileName = file.getName(); 
                byte[] fileContent = file.get(); 
                Files.write(Paths.get(fileName), fileContent);
                uploadedCount++;
            } catch (Exception exception) {
                // handle
            }
        }
        return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, 
          "Uploaded files " + uploadedCount + " out of " + files.size());
    } catch (IOException | FileUploadException e) {
        throw new IllegalArgumentException("Could not handle files from API request", e);
    }
    return newFixedLengthResponse(
      Response.Status.BAD_REQUEST, MIME_PLAINTEXT, "Error when uploading");
}

Hey, let's try it out:

> curl -F 'filename=@/pathToFile.txt' 'http://localhost:8080'
Uploaded files: 1

以上是在 PC 上的方式,现在改成 Android 方式。

build.gradle 中添加依赖:

implementation 'org.nanohttpd:nanohttpd:2.3.1'
implementation 'org.nanohttpd:nanohttpd-apache-fileupload:2.3.1'
implementation 'javax.servlet:javax.servlet-api:4.0.1'
implementation 'com.blankj:utilcodex:1.31.0'

创建 App 类:

public class App extends NanoHTTPD {
    public App(int port) throws IOException {
        super(8080);
        start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
    }

    @Override
    public Response serve(IHTTPSession session) {        
        try {
            List<FileItem> files
                    = new NanoFileUpload(new DiskFileItemFactory()).parseRequest(session);
            int uploadedCount = 0;
            for(FileItem file : files) {
                try {
                    String fileName = file.getName();
                    byte[] fileContent = file.get();
                    FileIOUtils.writeFileFromBytesByStream(PathUtils.getExternalDownloadsPath() + "/" + fileName,fileContent);
                    uploadedCount++;
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
            return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT,
                    "Uploaded files " + uploadedCount + " out of " + files.size());
        } catch (FileUploadException e) {
            throw new IllegalArgumentException("Could not handle files from API request", e);
        }
    }
}

FileItem 是一个接口,提供 get() 方法,将文件内容输出为字节组,同时也提供 getInputStream()getOutputStream() 来提供字节流,更多细节参考官方文档:Interface FileItem

FileIOUtils 是一个 Android 第三方库提供的工具类(AndroidUtilCode),writeFileFromBytesByStream() 方法将字节数组写入到对应的文件。

PathUtils 同样是 AndroidUtilCode 提供的针对 Android 文件夹路径的工具类,这里就不详细叙述了。

测试:

curl -F 'filename=@./e42ef5d7-3a30-4777-a988-ed561a73e2d9.xlsx' 'http://192.168.8.180:8080'

在 Android 的内部存储中的 Download 目录下即可看到上传的文件,另外如果文件名包含中文则是上传失败的。

下载文件

参考:How to download two or multiple files at a time in Android using NanoHTTPD?

这里对于下载文件是 GET 请求,而之前上传文件用的是 POST 请求。

public Response serve(IHTTPSession session)中添加对 GET 请求下载文件的处理:

// HTTP GET
if (session.getMethod() == Method.GET) {
    // 要下载的文件名称
    String fileName = Objects.requireNonNull(session.getParameters().get("download")).get(0);
    Log.d("fileName",fileName);
    if(!fileName.isEmpty()) {
        File downloadFile;
        downloadFile = new File(PathUtils.getExternalDownloadsPath() + "/" + fileName);
        return downloadFile(downloadFile);
    }
}

下载文件的请求命名为 curl 'http://ip:port/?download=e42ef5d7-3a30-4777-a988-ed561a73e2d9.xlsx',使用 download= 的方式来指定要下载的文件名。

创建下载文件函数 downloadFile()

/**
 * 下载文件
 * @param file 文件对象
 * @return 文件流
 */
private Response downloadFile(File file)
{
    FileInputStream fis = null;
    try
    {
        fis = new FileInputStream(file);
    } catch (FileNotFoundException ex)
    {
        Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
    }

    return newFixedLengthResponse(Response.Status.OK, "application/octet-stream", fis, file.length());
}

上传下载完整代码

import android.util.Log;
import com.blankj.utilcode.util.FileIOUtils;
import com.blankj.utilcode.util.PathUtils;
import fi.iki.elonen.NanoFileUpload;
import fi.iki.elonen.NanoHTTPD;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;

public class App extends NanoHTTPD {
    public App(int port) throws IOException {
        super(8080);
        start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
    }

    @Override
    public Response serve(IHTTPSession session) {

        // HTTP GET
        if (session.getMethod() == Method.GET) {
            // 要下载的文件名称
            String fileName = Objects.requireNonNull(session.getParameters().get("download")).get(0);
            Log.d("fileName",fileName);
            if(!fileName.isEmpty()) {
                File downloadFile;
                downloadFile = new File(PathUtils.getExternalDownloadsPath() + "/" + fileName);
                return downloadFile(downloadFile);
            }
        }
        // HTTP POST
        if (session.getMethod() == Method.POST) {
            // 判断是否是上传文件的 POST 请求
            if(Objects.requireNonNull(session.getHeaders().get("content-type")).split(";")[0].equals("multipart/form-data")) {
                // 处理 POST 方式的文件上传
                try {
                    List<FileItem> files
                            = new NanoFileUpload(new DiskFileItemFactory()).parseRequest(session);
                    int uploadedCount = 0;
                    for(FileItem file : files) {
                        try {
                            String fileName = file.getName();
                            byte[] fileContent = file.get();
                            FileIOUtils.writeFileFromBytesByStream(PathUtils.getExternalDownloadsPath() + "/" + fileName,fileContent);
                            uploadedCount++;
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                    return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT,
                            "Uploaded files " + uploadedCount + " out of " + files.size());
                } catch (FileUploadException e) {
                    throw new IllegalArgumentException("Could not handle files from API request", e);
                }
            }
            // 处理非文件上传类型的 POST
            Map<String, String> parms = new HashMap<String, String>();
            try {
                session.parseBody(parms);
                String requestBody = session.getQueryParameterString();
                return newFixedLengthResponse("Request body = " + requestBody);
            } catch (IOException | ResponseException e) {
                // handle
            }
        }

        return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_PLAINTEXT,
                "The requested resource does not exist");
    }

    /**
     * 下载文件
     * @param file 文件对象
     * @return 文件流
     */
    private Response downloadFile(File file)
    {
        FileInputStream fis = null;
        try
        {
            fis = new FileInputStream(file);
        } catch (FileNotFoundException ex)
        {
            Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
        }

        return newFixedLengthResponse(Response.Status.OK, "application/octet-stream", fis, file.length());
    }
}

标签: none

评论已关闭