Um guia para o NanoHTTPD
1. Introdução
NanoHTTPD é um servidor da web leve e de código aberto escrito em Java.
Neste tutorial, criaremos algumas APIs REST para explorar seus recursos.
2. Configuração do Projeto
Vamos adicionarNanoHTTPD core dependency ao nossopom.xml:
org.nanohttpd
nanohttpd
2.3.1
Para criar um servidor simples, precisamos estenderNanoHTTPDe substituir seu métodoserve:
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");
}
}
Definimos nossa porta de execução como8080e servidor para funcionar como um daemon (sem tempo limite de leitura).
Assim que iniciarmos o aplicativo, a URLhttp://localhost:8080/ retornará a mensagemHello world. Estamos usando o métodoNanoHTTPD#newFixedLengthResponse como uma maneira conveniente de construir um objetoNanoHTTPD.Response.
Vamos tentar nosso projeto comcURL:
> curl 'http://localhost:8080/'
Hello world
3. API REST
Na forma de métodos HTTP, o NanoHTTPD permite GET, POST, PUT, DELETE, HEAD, TRACE e vários outros.
Simplificando, podemos encontrar verbos HTTP suportados pelo método enum. Vamos ver como isso se desenrola.
3.1. HTTP GET
Primeiro, vamos dar uma olhada em GET. Digamos, por exemplo, que queremos retornar conteúdo somente quando o aplicativo receber uma solicitação GET.
Ao contrário deJava Servlet containers, não temos um métododoGet disponível - em vez disso, apenas verificamos o valor por meio degetMethod:
@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");
}
Isso foi bem simples, certo? Vamos fazer um teste rápido curvando nosso novo endpoint e ver se o parâmetro de solicitaçãoitemId é lido corretamente:
> curl 'http://localhost:8080/?itemId=23Bk8'
Requested itemId = 23Bk8
3.2. HTTP POST
Anteriormente, reagimos a um GET e lemos um parâmetro do URL.
Para cobrir os dois métodos HTTP mais populares, é hora de lidarmos com um POST (e, portanto, ler o corpo da solicitação):
@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");
}
Observe que antes, quando solicitávamos o corpo da solicitação,we first called the parseBody method. Isso porque queríamos carregar o corpo da solicitação para recuperação posterior.
Incluiremos um corpo em nosso comandocURL:
> curl -X POST -d 'deliveryAddress=Washington nr 4&quantity=5''http://localhost:8080/'
Request body = deliveryAddress=Washington nr 4&quantity=5
Os métodos HTTP restantes são muito semelhantes em natureza, portanto, vamos pular esses.
4. Compartilhamento de recursos entre origens
UsandoCORS, habilitamos a comunicação entre domínios. O caso de uso mais comum são as chamadas AJAX de um domínio diferente.
A primeira abordagem que podemos usar é habilitar o CORS para todas as nossas APIs. Usando o argumento–-cors, permitiremos o acesso a todos os domínios. Também podemos definir quais domínios permitimos com–cors=”http://dashboard.myApp.com http://admin.myapp.com”.
A segunda abordagem é ativar o CORS para APIs individuais. Vamos ver como usaraddHeader para conseguir isso:
@Override
public Response serve(IHTTPSession session) {
Response response = newFixedLengthResponse("Hello world");
response.addHeader("Access-Control-Allow-Origin", "*");
return response;
}
Agora, quando fizermoscURL, obteremos nosso cabeçalho CORS de volta:
> 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. Upload de arquivo
NanoHTTPD tem umdependency for file uploads separado, então vamos adicioná-lo ao nosso projeto:
org.nanohttpd
nanohttpd-apache-fileupload
2.3.1
javax.servlet
javax.servlet-api
4.0.1
provided
Observe queservlet-api dependency também é necessário (caso contrário, obteremos um erro de compilação).
O que o NanoHTTPD expõe é uma classe chamadaNanoFileUpload:
@Override
public Response serve(IHTTPSession session) {
try {
List 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");
}
Ei, vamos experimentar:
> curl -F '[email protected]/pathToFile.txt' 'http://localhost:8080'
Uploaded files: 1
6. Várias rotas
Umnanolet é como um servlet, mas tem um perfil muito baixo. We can use them to define many routes served by a single server (unlike previous examples with one route).
Em primeiro lugar, vamos adicionar odependency for nanolets necessário:
org.nanohttpd
nanohttpd-nanolets
2.3.1
E agora vamos estender nossa classe principal usandoRouterNanoHTTPD, para definir nossa porta de execução e fazer o servidor funcionar como um daemon.
O métodoaddMappings é onde definiremos nossos manipuladores:
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
}
}
A próxima etapa é definir nosso métodoaddMappings. Vamos definir alguns manipuladores.
O primeiro é uma classeIndexHandler para o caminho “/”. Esta classe vem com a biblioteca NanoHTTPD e retorna por padrão uma mensagemHello World. Podemos substituir o métodogetText quando quisermos uma resposta diferente:
addRoute("/", IndexHandler.class); // inside addMappings method
E para testar nossa nova rota, podemos fazer:
> curl 'http://localhost:8080'
Hello world!
Em segundo lugar, vamos criar uma nova classeUserHandler que estende aDefaultHandler. existente. A rota para ela será /users. Aqui brincamos com o texto, o tipo MIME e o código de status retornado:
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;
}
}
Para chamar essa rota, emitiremos um comandocURL novamente:
> curl -X POST 'http://localhost:8080/users'
UserA, UserB, UserC
Finalmente, podemos explorarGeneralHandler com uma nova classeStoreHandler. Modificamos a mensagem retornada para incluir a seçãostoreId do URL.
public static class StoreHandler extends GeneralHandler {
@Override
public Response get(
UriResource uriResource, Map urlParams, IHTTPSession session) {
return newFixedLengthResponse("Retrieving store for id = "
+ urlParams.get("storeId"));
}
}
Vamos verificar nossa nova API:
> curl 'http://localhost:8080/stores/123'
Retrieving store for id = 123
7. HTTPS
Para usar o HTTPS, precisamos de um certificado. Por favor, consulteour article on SSL para informações mais detalhadas.
Podemos usar um serviço comoLet’s Encrypt ou podemos simplesmente gerar um certificado autoassinado da seguinte maneira:
> 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
Em seguida, copiaríamos estekeystore.jks para um local em nosso classpath, como digamos a pastasrc/main/resources de um projeto Maven.
Depois disso, podemos referenciá-lo em uma chamada paraNanoHTTPD#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
}
E agora podemos experimentar. Observe o uso do parâmetro -insecure, porquecURL não será capaz de verificar nosso certificado autoassinado por padrão:
> curl --insecure 'https://localhost:8443'
HTTPS call is a success
8. WebSockets
NanoHTTPD suportaWebSockets.
Vamos criar a implementação mais simples de um WebSocket. Para isso, precisamos estender a classeNanoWSD. Também precisaremos adicionar oNanoHTTPD dependency for WebSocket:
org.nanohttpd
nanohttpd-websocket
2.3.1
Para nossa implementação, responderemos apenas com uma carga útil de texto simples:
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
}
}
}
}
Em vez decURL desta vez, usaremoswscat:
> wscat -c localhost:8080
hello
hello to you
bye
bye to you
9. Conclusão
Para resumir, criamos um projeto que usa a biblioteca NanoHTTPD. Em seguida, definimos APIs RESTful e exploramos mais funcionalidades relacionadas ao HTTP. No final, também implementamos um WebSocket.
A implementação de todos esses snippets está disponívelover on GitHub.