NanoHTTPD指南

A Guide to NanoHTTPD

1.简介

NanoHTTPD是用Java编写的开源,轻量级的Web服务器。

在本教程中,我们将创建一些REST API来探索其功能。

2.项目设置

让我们 将NanoHTTPD核心依赖项添加到ourpom.xml中:

1
2
3
4
5
<dependency>
    <groupId>org.nanohttpd</groupId>
    <artifactId>nanohttpd</artifactId>
    <version>2.3.1</version>
</dependency>

要创建一个简单的服务器,我们需要扩展NanoHTTPD并覆盖其服务方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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尝试我们的项目:

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

3. REST API

通过HTTP方法,NanoHTTPD允许GET,POST,PUT,DELETE,HEAD,TRACE等。

简而言之,我们可以通过方法enum找到支持的HTTP动词。 让我们看看如何进行。

3.1。 HTTP GET

首先,让我们看一下GET。 例如,假设我们只想在应用程序收到GET请求时才返回内容。

与Java Servlet容器不同,我们没有可用的doGetmethod,而是通过getMethod检查值:

1
2
3
4
5
6
7
8
9
@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:

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

3.2。 HTTP POST

我们之前对GET做出了反应,并从URL中读取了一个参数。

为了涵盖两种最流行的HTTP方法,是时候来处理POST(并阅读请求正文)了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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");
}

我们将在cURL命令中包含一个正文:

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

其余的HTTP方法本质上非常相似,因此我们将跳过这些方法。

4.跨域资源共享

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

现在,当我们使用cURL时,我们将获得CORS标头:

1
2
3
4
5
6
7
8
9
> 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对文件上传具有单独的依赖关系,因此让我们将其添加到我们的项目中:

1
2
3
4
5
6
7
8
9
10
11
<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的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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");
}

嘿,让我们尝试一下:

1
2
> curl -F '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="e88e81848d8689858dd5a8">[emailprotected]</a>/pathToFile.txt' 'http://localhost:8080'
Uploaded files: 1

6.多条路线

首先,让我们为nanolet添加所需的依赖项:

1
2
3
4
5
<dependency>
    <groupId>org.nanohttpd</groupId>
    <artifactId>nanohttpd-nanolets</artifactId>
    <version>2.3.1</version>
</dependency>

现在,我们将使用RouterNanoHTTPD扩展主类,定义我们的运行端口,并使服务器作为守护程序运行。

在addMappings方法中,我们将定义处理程序:

1
2
3
4
5
6
7
8
9
10
11
12
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方法:

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

为了测试我们的新路线,我们可以执行以下操作:

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

其次,让我们创建一个新的UserHandler 类,以扩展现有的DefaultHandler。 路由是/ users。 在这里,我们尝试使用文本,MIME类型和返回的状态代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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命令:

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

最后,我们可以使用新的StoreHandler类探索GeneralHandler。 我们修改了返回的消息,以包括URL的storeId部分。

1
2
3
4
5
6
7
8
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:

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

7. HTTPS

为了使用HTTPS,我们需要一个证书。 请参阅我们关于SSL的文章,以获取更深入的信息。

我们可以使用"让我们加密"之类的服务,也可以简单地生成一个自签名证书,如下所示:

1
2
3
> 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复制到我们的类路径上的某个位置,例如aMaven项目的src / main / resources文件夹。

之后,我们可以在对NanoHTTPD#makeSSLSocketFactory的调用中引用它:

1
2
3
4
5
6
7
8
9
10
11
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无法验证我们的自签名证书:

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

8. WebSockets

NanoHTTPD支持WebSockets。

让我们创建一个最简单的WebSocket实现。 为此,我们需要扩展NanoWSD类。 我们还需要为WebSocket添加NanoHTTPD依赖项:

1
2
3
4
5
<dependency>
    <groupId>org.nanohttpd</groupId>
    <artifactId>nanohttpd-websocket</artifactId>
    <version>2.3.1</version>
</dependency>

对于我们的实现,我们将使用一个简单的文本有效负载进行回复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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:

1
2
3
4
5
> wscat -c localhost:8080
hello
hello to you
bye
bye to you

9.结论

总结起来,我们创建了一个使用TheNanoHTTPD库的项目。 接下来,我们定义了RESTful API,并探索了更多与HTTP相关的功能。 最后,我们还实现了一个WebSocket。

所有这些代码片段的实现都可以在GitHub上获得。