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 world
3. 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 = 23Bk8
3.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=5
The 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 world
5. 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: 1
6. 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 method
And 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, UserC
Finally, 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 = 123
7. 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 9999
Next, 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 success
8. 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 you
9. 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上找到,。