1. Introduction
1.绪论
NanoHTTPD is an open-source, lightweight, web server written in Java.
NanoHTTPD是一个开源的、轻量级的、用Java编写的Web服务器。
In this tutorial, we’ll create a few REST APIs to explore its features.
在本教程中,我们将创建一些REST API来探索其功能。
2. Project Setup
2.项目设置
Let’s add the NanoHTTPD core dependency to our pom.xml:
让我们在我们的pom.xml中添加NanoHTTPD核心依赖项:
<dependency>
    <groupId>org.nanohttpd</groupId>
    <artifactId>nanohttpd</artifactId>
    <version>2.3.1</version>
</dependency>To create a simple server, we need to extend NanoHTTPD and override its serve method:
为了创建一个简单的服务器,我们需要扩展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");
    }
}We defined our running port as 8080 and server to work as a daemon (no read timeout).
我们将运行端口定义为8080,服务器作为一个守护程序工作(没有读取超时)。
Once we’ll start the application, the URL http://localhost:8080/ will return the Hello world message. We’re using NanoHTTPD#newFixedLengthResponse method as a convenient way of building a NanoHTTPD.Response object.
一旦我们启动应用程序,URL http://localhost:8080/将返回Hello world消息。我们使用NanoHTTPD#newFixedLengthResponse方法作为构建NanoHTTPD.Response对象的方便方法。
Let’s try our project with cURL:
让我们用cURL试试我们的项目。
> curl 'http://localhost:8080/'
Hello world3. REST API
3.REST API
In the way of HTTP methods, NanoHTTPD allows GET, POST, PUT, DELETE, HEAD, TRACE, and several others.
在HTTP方法方面,NanoHTTPD允许GET、POST、PUT、DELETE、HEAD、TRACE和其他几种方法。
Simply put, we can find supported HTTP verbs via the method enum. Let’s see how this plays out.
简单地说,我们可以通过方法枚举找到支持的HTTP动词。让我们来看看这一点是如何实现的。
3.1. HTTP GET
HTTP GET
First, let’s take a look at GET. Say, for example, that we want to return content only when the application receives a GET request.
首先,让我们看一下GET。比如说,我们想只在应用程序收到GET请求时返回内容。
Unlike Java Servlet containers, we don’t have a doGet method available – instead, we just check the value via getMethod:
与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");
}That was pretty simple, right? Let’s run a quick test by curling our new endpoint and see that the request parameter itemId is read correctly:
这很简单,对吗?让我们通过卷曲我们的新端点运行一个快速测试,看看请求参数itemId是否被正确读取。
> curl 'http://localhost:8080/?itemId=23Bk8'
Requested itemId = 23Bk83.2. HTTP POST
3.2 HTTP POST
We previously reacted to a GET and read a parameter from the URL.
我们之前对一个GET作出反应,从URL中读取一个参数。
In order to cover the two most popular HTTP methods, it’s time for us to handle a POST (and thus read the request body):
为了涵盖两种最流行的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");
}We’ll include a body in our cURL command:
我们将在我们的cURL命令中包含一个主体。
> curl -X POST -d 'deliveryAddress=Washington nr 4&quantity=5''http://localhost:8080/'
Request body = deliveryAddress=Washington nr 4&quantity=5The remaining HTTP methods are very similar in nature, so we’ll skip those.
其余的HTTP方法在性质上非常相似,所以我们将跳过这些。
4. Cross-Origin Resource Sharing
4.跨源头资源共享
@Override 
public Response serve(IHTTPSession session) {
    Response response = newFixedLengthResponse("Hello world"); 
    response.addHeader("Access-Control-Allow-Origin", "*");
    return response;
}Now when we cURL, we’ll get our CORS header back:
现在当我们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 world5. File Upload
5.文件上传
NanoHTTPD has a separate dependency for file uploads, so let’s add it to our project:
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>Please note that the servlet-api dependency is also needed (otherwise we’ll get a compilation error).
请注意,还需要servlet-api依赖性(否则我们会得到一个编译错误)。
What NanoHTTPD exposes is a class called NanoFileUpload:
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");
}Hey, let’s try it out:
嘿,让我们试试吧。
> curl -F 'filename=@/pathToFile.txt' 'http://localhost:8080'
Uploaded files: 16. Multiple Routes
6.多条路线
Firstly, let’s add the required dependency for nanolets:
首先,让我们为nanolets添加所需的依赖。
<dependency>
    <groupId>org.nanohttpd</groupId>
    <artifactId>nanohttpd-nanolets</artifactId>
    <version>2.3.1</version>
</dependency>And now we’ll extend our main class using the RouterNanoHTTPD, define our running port and have the server run as a daemon.
现在我们将使用RouterNanoHTTPD扩展我们的主类,定义我们的运行端口,并让服务器作为一个守护程序运行。
The addMappings method is where we’ll define our handlers:
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
    }
}The next step is to define our addMappings method. Let’s define a few handlers.
下一步是定义我们的addMappings方法。让我们定义几个处理程序。
The first one is an IndexHandler class to “/” path. This class comes with the NanoHTTPD library and returns by default a Hello World message. We can override the getText method when we want a different response:
第一个是一个IndexHandler类到”/”路径。这个类是NanoHTTPD库中的,默认返回一个Hello World消息。当我们想要一个不同的响应时,我们可以覆盖getText方法:。
addRoute("/", IndexHandler.class); // inside addMappings methodAnd to test our new route we can do:
而为了测试我们的新路线,我们可以做。
> curl 'http://localhost:8080' 
<html><body><h2>Hello world!</h3></body></html>Secondly, let’s create a new UserHandler class which extends the existing DefaultHandler. The route for it will be /users. Here we played around with the text, MIME type, and the status code returned:
其次,让我们创建一个新的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;
    }
}To call this route we’ll issue a cURL command again:
为了调用这个路由,我们将再次发出一个cURL命令。
> curl -X POST 'http://localhost:8080/users' 
UserA, UserB, UserCFinally, we can explore the GeneralHandler with a new StoreHandler class. We modified the returned message to include the storeId section of the URL.
最后,我们可以用一个新的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"));
    }
}Let’s check our new API:
让我们检查一下我们的新API。
> curl 'http://localhost:8080/stores/123' 
Retrieving store for id = 1237. HTTPS
7.HTTPS
In order to use the HTTPS, we’ll need a certificate. Please refer to our article on SSL for more in-depth information.
为了使用HTTPS,我们将需要一个证书。请参考我们关于SSL的文章,以获得更深入的信息。
We could use a service like Let’s Encrypt or we can simply generate a self-signed certificate as follows:
我们可以使用像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 9999Next, we’d copy this keystore.jks to a location on our classpath, like say the src/main/resources folder of a Maven project.
接下来,我们把这个keystore.jks复制到classpath上的某个位置,比如Maven项目的src/main/resources文件夹。
After that, we can reference it in a call to NanoHTTPD#makeSSLSocketFactory:
之后,我们可以在调用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
}And now we can try it out. Please notice the use of the —insecure parameter, because cURL won’t be able to verify our self-signed certificate by default:
现在我们可以试试了。请注意使用了-insecure参数,因为cURL默认情况下无法验证我们的自签名证书。
> curl --insecure 'https://localhost:8443'
HTTPS call is a success8. WebSockets
8.WebSockets
NanoHTTPD supports WebSockets.
NanoHTTPD支持WebSockets>。
Let’s create the simplest implementation of a WebSocket. For this, we’ll need to extend the NanoWSD class. We’ll also need to add the NanoHTTPD dependency for WebSocket:
让我们创建一个最简单的WebSocket的实现。为此,我们需要扩展NanoWSD类。我们还需要为WebSocket添加NanoHTTPD依赖项:。
<dependency>
    <groupId>org.nanohttpd</groupId>
    <artifactId>nanohttpd-websocket</artifactId>
    <version>2.3.1</version>
</dependency>For our implementation, we’ll just reply with a simple text payload:
在我们的实现中,我们将只用一个简单的文本有效载荷来回复。
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
            }
        }
    }
}Instead of cURL this time, we’ll use wscat:
这次我们不使用cURL,而使用wscat。
> wscat -c localhost:8080
hello
hello to you
bye
bye to you9. Conclusion
9.结语
To sum it up, we’ve created a project that uses the NanoHTTPD library. Next, we defined RESTful APIs and explored more HTTP related functionalities. In the end, we also implemented a WebSocket.
总而言之,我们已经创建了一个使用NanoHTTPD库的项目。接下来,我们定义了RESTful APIs,并探索了更多的HTTP相关功能。最后,我们还实现了一个WebSocket。
The implementation of all these snippets is available over on GitHub.
所有这些片段的实现都可以在GitHub上找到,。