1. 简介

NanoHTTPD 是一个轻量级的开源 Java Web 服务器实现。它简单、嵌入式,适合用于小型项目、本地服务、测试环境或 IoT 场景。

本教程中,我们将使用 NanoHTTPD 创建几个 REST 接口,探索其核心功能。


2. 项目初始化

首先,在 pom.xml 中添加 NanoHTTPD 核心依赖:

<dependency>
    <groupId>org.nanohttpd</groupId>
    <artifactId>nanohttpd</artifactId>
    <version>2.3.1</version>
</dependency>

然后创建一个简单的 HTTP 服务:

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

    public static void main(String[] args) throws IOException {
        new App();
    }

    @Override
    public Response serve(IHTTPSession session) {
        return newFixedLengthResponse("Hello world");
    }
}

上面的代码启动了一个监听 8080 端口的 HTTP 服务,访问 http://localhost:8080/ 将返回 Hello world

使用 cURL 验证:

curl 'http://localhost:8080/'
Hello world

3. REST API 支持

NanoHTTPD 支持 GET、POST、PUT、DELETE 等常见 HTTP 方法。

3.1. GET 请求处理

要处理 GET 请求,我们可以通过 session.getMethod() 判断请求类型,并从 session.getParameters() 获取参数:

@Override
public Response serve(IHTTPSession session) {
    if (session.getMethod() == Method.GET) {
        String itemId = session.getParameters().get("itemId").get(0);
        return newFixedLengthResponse("Requested itemId = " + itemId);
    }
    return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_PLAINTEXT, 
        "The requested resource does not exist");
}

测试:

curl 'http://localhost:8080/?itemId=23Bk8'
Requested itemId = 23Bk8

3.2. POST 请求处理

处理 POST 请求需要调用 session.parseBody() 来解析请求体:

@Override
public Response serve(IHTTPSession session) {
    if (session.getMethod() == Method.POST) {
        try {
            session.parseBody(new HashMap<>());
            String body = session.getQueryParameterString();
            return newFixedLengthResponse("Request body = " + body);
        } catch (IOException | ResponseException e) {
            // handle exception
        }
    }
    return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_PLAINTEXT, 
        "The requested resource does not exist");
}

测试:

curl -X POST -d 'deliveryAddress=Washington nr 4&quantity=5' 'http://localhost:8080/'
Request body = deliveryAddress=Washington nr 4&quantity=5

其他方法如 PUT、DELETE 类似,此处省略。


4. 跨域请求支持 (CORS)

通过在响应中添加 Access-Control-Allow-Origin 头即可支持跨域请求:

@Override
public Response serve(IHTTPSession session) {
    Response response = newFixedLengthResponse("Hello world");
    response.addHeader("Access-Control-Allow-Origin", "*");
    return response;
}

验证:

curl -v 'http://localhost:8080'
...
Access-Control-Allow-Origin: *
...

5. 文件上传

NanoHTTPD 提供了独立的文件上传模块。添加依赖:

<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>

然后使用 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[] content = file.get();
                Files.write(Paths.get(fileName), content);
                uploadedCount++;
            } catch (Exception e) {
                // 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);
    }
}

测试:

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

6. 多路由支持

NanoHTTPD 提供了 RouterNanoHTTPD 类用于支持多个路由。添加依赖:

<dependency>
    <groupId>org.nanohttpd</groupId>
    <artifactId>nanohttpd-nanolets</artifactId>
    <version>2.3.1</version>
</dependency>

创建主类并注册路由:

public class MultipleRoutesExample extends RouterNanoHTTPD {
    public MultipleRoutesExample() throws IOException {
        super(8080);
        addMappings();
        start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
    }

    @Override
    public void addMappings() {
        addRoute("/", IndexHandler.class);
    }

    public static void main(String[] args) throws IOException {
        new MultipleRoutesExample();
    }
}

定义 UserHandler 示例:

public static class UserHandler extends DefaultHandler {
    @Override
    public String getText() {
        return "UserA, UserB, UserC";
    }

    @Override
    public String getMimeType() {
        return MIME_PLAINTEXT;
    }

    @Override
    public Response.IStatus getStatus() {
        return Response.Status.OK;
    }
}

绑定路由:

addRoute("/users", UserHandler.class);

测试:

curl -X POST 'http://localhost:8080/users'
UserA, UserB, UserC

动态路由示例:

public static class StoreHandler extends GeneralHandler {
    @Override
    public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {
        return newFixedLengthResponse("Retrieving store for id = " + urlParams.get("storeId"));
    }
}

绑定:

addRoute("/stores/:storeId", StoreHandler.class);

测试:

curl 'http://localhost:8080/stores/123'
Retrieving store for id = 123

7. HTTPS 支持

要启用 HTTPS,你需要一个证书。可以使用 Let’s Encrypt 或生成自签名证书:

keytool -genkey -keyalg RSA -alias selfsigned \
  -keystore keystore.jks -storepass password -validity 360 \
  -keysize 2048 -ext SAN=DNS:localhost,IP:127.0.0.1 -validity 9999

keystore.jks 放入资源目录(如 src/main/resources),并在代码中加载:

public class HttpsExample extends NanoHTTPD {
    public HttpsExample() throws IOException {
        super(8080);
        makeSecure(NanoHTTPD.makeSSLSocketFactory("/keystore.jks", "password".toCharArray()), null);
        start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
    }

    @Override
    public Response serve(IHTTPSession session) {
        return newFixedLengthResponse("HTTPS call is a success");
    }

    public static void main(String[] args) throws IOException {
        new HttpsExample();
    }
}

测试:

curl --insecure 'https://localhost:8080'
HTTPS call is a success

⚠️ 注意:--insecure 是因为使用了自签名证书。


8. WebSocket 支持

NanoHTTPD 同样支持 WebSocket。添加依赖:

<dependency>
    <groupId>org.nanohttpd</groupId>
    <artifactId>nanohttpd-websocket</artifactId>
    <version>2.3.1</version>
</dependency>

创建 WebSocket 服务端:

public class WsdExample extends NanoWSD {
    public WsdExample() throws IOException {
        super(8080);
        start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
    }

    @Override
    protected WebSocket openWebSocket(IHTTPSession ihttpSession) {
        return new WsdSocket(ihttpSession);
    }

    private static class WsdSocket extends WebSocket {
        public WsdSocket(IHTTPSession handshakeRequest) {
            super(handshakeRequest);
        }

        @Override
        protected void onMessage(WebSocketFrame frame) {
            try {
                send(frame.getTextPayload() + " to you");
            } catch (IOException e) {
                // handle
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new WsdExample();
    }
}

测试使用 wscat

wscat -c localhost:8080
hello
hello to you
bye
bye to you

9. 总结

NanoHTTPD 是一个轻量、灵活的嵌入式 Java Web 服务器库,适用于本地服务、测试、原型开发等场景。

✅ 优点:

  • 简洁 API
  • 无需外部容器
  • 支持 HTTPS 和 WebSocket
  • 易于扩展

❌ 缺点:

  • 不适合高并发、复杂业务场景
  • 缺乏 Spring 级别的 MVC 支持

适合场景:

  • 内部服务
  • 嵌入式设备
  • 快速原型
  • 本地调试工具

完整代码示例可参考:GitHub 示例仓库


原始标题:A Guide to NanoHTTPD | Baeldung