A Guide to NanoHTTPD
1.简介
NanoHTTPD是用Java编写的开源,轻量级的Web服务器。
在本教程中,我们将创建一些REST API来探索其功能。
2.项目设置
让我们 span> 将NanoHTTPD核心依赖项添加到ourpom.xml中: span>
1 2 3 4 5 | <dependency> <groupId>org.nanohttpd</groupId> <artifactId>nanohttpd</artifactId> <version>2.3.1</version> </dependency> |
要创建一个简单的服务器,我们需要扩展NanoHTTPD并覆盖其服务方法: span>
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,并将服务器定义为守护程序(无读取超时)。 span>
一旦启动应用程序,URL http:// localhost:8080 /将返回Hello world消息。 我们使用NanoHTTPD#newFixedLengthResponse方法作为构建NanoHTTPD.Response对象的便捷方法。
span>
让我们用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标头: span>
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对文件上传具有单独的依赖关系,因此让我们将其添加到我们的项目中: span>
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方法。 让我们定义一些处理程序。 span>
第一个是 span> 到" /"路径的IndexHandler类。 此类带有NanoHTTPD库,默认情况下返回Hello World消息。 当我们想要不同的响应时,可以覆盖getText方法: span>
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类型和返回的状态代码: span>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
要调用此路由,我们将再次发出cURL命令:
1 2 | > curl -X POST 'http://localhost:8080/users' UserA, UserB, UserC |
最后,我们可以使用新的StoreHandler类探索GeneralHandler。 我们修改了返回的消息,以包括URL的storeId部分。 span>
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上获得。