知識庫 / REST RSS 訂閱

NanoHTTPD 指南

REST
HongKong
4
03:50 AM · Dec 06 ,2025

1. 引言

NanoHTTPD 是一個開源、輕量級的 Java 編寫的 Web 服務器。

在本教程中,我們將創建幾個 REST API 以探索其功能。

2. 項目設置

讓我們添加 NanoHTTPD 核心依賴項到我們的 pom.xml

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

要創建一個簡單的服務器,我們需要擴展 NanoHTTPD 並覆蓋其中的 serve 方法:

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,服務器以守護進程(無讀取超時)模式運行。

啓動應用程序後,URL http://localhost:8080/ 將返回 Hello world 消息。我們使用 NanoHTTPD#newFixedLengthResponse 方法作為構建 NanoHTTPD.Response 對象的便捷方式。

讓我們使用 cURL 嘗試我們的項目:

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

3. REST API

NanoHTTPD 支持 HTTP 方法,包括 GET、POST、PUT、DELETE、HEAD、TRACE 等多種方法。

簡單來説,我們可以通過 method 枚舉來獲取支持的 HTTP 動詞。下面我們來具體看看如何應用這些動詞。

3.1. HTTP GET

首先,讓我們來看一下 HTTP GET 方法。 比如,我們希望在應用程序收到 GET 請求時才返回內容。

與 Java Servlet 容器不同,我們沒有 doGet 方法可用——我們只是通過 getMethod 方法來檢查其值。

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

這確實很簡單,對吧?我們來運行一個快速測試,通過調用我們新的端點,並確認請求參數 itemId 被正確讀取:

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

3.2. HTTP POST

我們之前已經對 GET 請求進行了處理,並從 URL 中讀取了參數。

為了覆蓋兩種最流行的 HTTP 方法,現在我們將處理 POST 請求(從而讀取請求體):

@Override
public Response serve(IHTTPSession session) {
    if (session.getMethod() == Method.POST) {
        try {
            session.parseBody(new HashMap<>());
            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");
}
<div>
  <div>
    <div>
      <span>請注意,在請求體被請求之前,<strong >我們首先調用了 <em >parseBody</em> 方法。</strong> 這是因為我們希望加載請求體以供稍後檢索。</span>
    </div>
    <p>我們將在 <em >cURL</em> 命令中包含一個請求體:</p>
  </div>
</div>
> curl -X POST -d 'deliveryAddress=Washington nr 4&quantity=5''http://localhost:8080/'
Request body = deliveryAddress=Washington nr 4&quantity=5

剩餘的 HTTP 方法在本質上非常相似,因此我們不會詳細介紹它們。

4. 跨域資源共享 (CORS)

使用CORS我們可以啓用跨域通信。最常見的用例是來自不同域的AJAX調用。
第一種方法是我們為所有API啓用CORS。通過使用–cors參數,我們將允許訪問所有域。我們還可以使用–cors=”http://dashboard.myApp.com http://admin.myapp.com”定義我們允許訪問的域。
第二種方法是為單個API啓用CORS。下面是如何使用addHeader來實現此目的:
@Override 
public Response serve(IHTTPSession session) {
    Response response = newFixedLengthResponse("Hello world"); 
    response.addHeader("Access-Control-Allow-Origin", "*");
    return response;
}

現在當我們使用 cURL 時,我們會獲得我們的 CORS 頭部:

> curl -v 'http://localhost:8080'
HTTP/1.1 200 OK 
Content-Type: text/html
Date: Thu, 13 Jun 2019 03:58:14 GMT
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 11

Hello world

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>

請注意,servlet-api 依賴項 也需要添加(否則我們會遇到編譯錯誤)。

NanoHTTPD 提供的類是 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");
}

好,我們來試試看:

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

6. 多重路由 (Multiple Routes)

一個 Nanolet 就像一個 Servlet,但具有極低的侵入性。我們可以使用它們來定義多個路由,這些路由由單個服務器服務(與之前的示例不同,之前的示例只有一個路由)

首先,讓我們添加所需的 依賴項用於 Nanolet

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

現在我們將使用 RouterNanoHTTPD擴展我們的主類,定義運行端口並使服務器以守護進程方式運行。

addMappings方法是定義處理程序的所在位置:

public class MultipleRoutesExample extends RouterNanoHTTPD {
    public MultipleRoutesExample() throws IOException {
        super(8080);
        addMappings();
        start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
    }
 
    @Override
    public void addMappings() {
        // todo fill in the routes
    }
}

下一步是定義我們的 addMappings 方法。我們先定義幾個處理器。

第一個是 IndexHandler 類,用於“/”路徑。該類來自 NanoHTTPD 庫,默認返回 Hello World 消息。我們可以通過覆蓋 getText 方法來獲得不同的響應:

addRoute("/", IndexHandler.class); // inside addMappings method

為了測試我們新的路線,我們可以執行以下操作:

> curl 'http://localhost:8080' 
<html><body><h2>Hello world!</h3></body></html>

第二,我們創建一個新的 UserHandler 類,該類繼承自現有的 DefaultHandler。它的路由將是 /users。在這裏,我們對文本、MIME 類型和返回的狀態碼進行了試驗:

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

為了調用此路由,我們將再次發出一個 cURL 命令:

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

最後,我們可以使用新的 StoreHandler 類來探索 GeneralHandler。我們修改了返回的消息,使其包含 URL 中的 storeId 部分。

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"));
    }
}

讓我們檢查我們的新 API:

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

7. HTTPS

為了使用 HTTPS,我們需要一個證書。請參閲我們關於 SSL 的文章以獲取更深入的信息。

我們可以使用像 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 文件到 classpath 上的某個位置,例如 Maven 項目中 src/main/resources 文件夾。

之後,我們可以將其引用到對 NanoHTTPD#makeSSLSocketFactory 的調用中。

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);
    }

    // main and serve methods
}

現在我們可以嘗試一下。請注意使用 —insecure 參數,因為 cURL 默認情況下無法驗證我們的自簽名證書:

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

8. WebSockets

NanoHTTPD 支持 WebSockets。

讓我們創建一個最簡單的 WebSocket 實現。為此,我們需要擴展 NanoWSD 類。 此外,還需要添加 NanoHTTPD 的 WebSocket 依賴項:

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

在我們的實現中,我們將僅使用簡單的文本負載進行回覆:

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

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

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

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

        //override onOpen, onClose, onPong and onException methods

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

與其這次使用 cURL,我們這次將使用 wscat

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

9. 結論

總而言之,我們創建了一個使用 NanoHTTPD 庫的項目。接下來,我們定義了 RESTful API 並探索了更多與 HTTP 相關的功能。最終,我們還實現了 WebSocket。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.