Como criar um Servidor Web Java Sem Framework
Diogo Izele
Posted on April 20, 2023
Olá, espero que você esteja bem 😊 Uma das coisas que gosto de fazer ao iniciar meus estudos em uma tecnologia, é entender como as coisas funcionam nos níveis mais básico da ferramenta, ou seja, construir algo sem o auxílio de frameworks. Assim, passo a ter uma boa noção do que é framework e o que é a nativo da ferramente.
A proposta aqui é criar e configurar o projeto de um servidor backend simples na linguagem Java, que execute no ambiente local e possua uma rota get
que imprima uma mensagem de “Hello, World!” padrão. Ademais, a ideia é codificar o servidor tentando utilizar o nível mais básico e nativo e entender o que cada ferramenta ou configuração altera no projeto.
Quais são os pré-requisitos?
Para iniciar, como quero o menor auxílio possível de outras ferramentas, não vou escolher uma IDE JAVA, uma vez que esse software me ajudaria nessa parte de configuração inicial, não me deixando livre para percorrer o caminho das pedras que é meu propósito. Por isso, o editor de texto que estarei usando é o Visual Studio Code.
A escolha do VS-code não é influenciada por ser o editor que mais gosto 🤐 mas sim por eu precisar de uma ferramenta que me auxilie o mínimo, mas que ainda torne esse processo operacional. Diferente de utilizar um bloco de notas, que não mostra em destaque as palavras chave ou no Vi ou Nano que são editores que rodam no terminal, que eu particularmente tenho dificuldade de navegar entre o código.
Além do editor de texto, preciso é claro, do JDK. A sigla para Java Development Kit, compreende o conjunto de ferramentas que os desenvolvedores usam para criar aplicativos em Java. Dentro dele estão inclusos o compilador, depuradores, documentadores e outras ferramentas.
Para saber se você possui o JDK instalado em sua máquina, no terminal execute o comando
java -version
javac -version
Como saída, você deve ver algo parecido com isso, variando a versão do Java que você instalou. Para este projeto estou usando a versão 11.0.7
do Java.
Se você não tiver o JDK instalado, você pode baixá-lo e instalá-lo a partir do site oficial da Oracle ou do OpenJDK.
Familiarizando-se com o Java
Com tudo configurado, vou fazer um pequeno teste criando um arquivo que imprime ”Hello, World!”, compilar e executar através do terminal.
No diretório do projeto, vou criar a pasta src
onde ficará os arquivos fontes do projeto, e dentro dela, o arquivo Main.java
. Perceba que a extensão do arquivo é .java
, isso indica que se trata de um arquivo fonte, ou seja, o arquivo onde o programador escreverá o código propriamente dito. Dentro desse arquivo estarei criando a classe que leva o mesmo nome, e o método main
.
public class Main {
public static void main(String[] args) {
String greeting = "Hello, World!";
System.out.println(greeting);
}
}
O método public static void main(String[] args)
em Java é o ponto de entrada para qualquer programa em Java. É o método que é executado primeiro quando você executa um programa Java a partir da linha de comando ou do IDE. Em programação, funções main semelhantes à essa são chamadas de Entry Point.
Com o código criado, o próximo passo é a compilação. Para isso, através de um terminal, navegue até a pasta do projeto e execute o seguinte comando:
javac src/Main.java
Se não apareceu nenhuma saída no terminal, no diretório src se encontra o bytecode do nosso programa em Java. Bytecode é uma forma de representar um programa Java compilado em uma linguagem intermediária que é interpretada pela Java Virtual Machine (JVM).
Agora, para executar nosso programa utilizamos o comando:
java -cp src Main
O parâmetro -cp
especifica o classpath, ou seja, o caminho onde a JVM deve procurar pelas classes referenciadas pelo programa. Neste caso, estamos especificando que a pasta "src" contém as classes do programa.
Hora do show
Com tudo pronto, testado e funcionando, podemos avançar para a parte que de criação do nosso servidor propriamente dito. Para isso teremos que fazer a importação de 3 módulos do Java.
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
public class Main {
public static void main(String[] args) {
String greeting = "Hello, World!";
System.out.println(greeting);
}
}
A razão pela qual você precisa fazer o import desses módulos é que eles não estão incluídos na biblioteca padrão do Java. A classe com.sun.net.httpserver.HttpServer
pertence à API do servidor HTTP incorporado, que é uma parte do JDK, mas não é incluída por padrão em todos os ambientes de execução do Java. Da mesma forma, as classes java.io.IOException
e java.net.InetSocketAddress
são parte da biblioteca padrão do Java, mas precisam ser importadas porque são usadas no código e não estão no pacote padrão.
Além disso, mesmo que as classes estejam no pacote padrão, é uma boa prática importá-las explicitamente para deixar o código mais claro e fácil de entender.
com.sun.net.httpserver.HttpServer
Esse é o módulo da API do servidor HTTP incorporado que é fornecido pelo JDK. Essa API permite que você crie um servidor web em Java sem precisar usar uma biblioteca de terceiros ou um servidor web externo. A classe HttpServer
representa o servidor web em si, e é responsável por gerenciar as conexões de entrada e saída, bem como pelo roteamento das solicitações para as classes de manipuladores correspondentes.
java.io.IOException
Este import é necessário para que o servidor HTTP incorporado possa lançar uma exceção de IOException
. Isso acontece porque o servidor precisa criar um (socket) para escutar as conexões de entrada. No entanto, pode haver problemas ao criar o soquete, como falha na conexão, porta em uso ou outro erro. A exceção IOException
é lançada quando ocorre um erro na criação do soquete. Essa exceção é uma exceção verificada, o que significa que o código precisa lidar com ela de alguma forma (como mostrado na resposta anterior).
java.net.InetSocketAddress
Este import é necessário porque a classe HttpServer
precisa de um objeto InetSocketAddress
para determinar qual porta e endereço IP usar para escutar as conexões de entrada. É por isso que a criação de um objeto InetSocketAddress
é um passo importante na criação de um servidor HTTP incorporado, porque ele especifica onde o servidor web estará "ouvindo" as solicitações HTTP recebidas.
Repare que até aqui, apenas fizemos os imports necessários para os módulos básicos, disponíveis no JDK, para a criação do nosso servidor web.
Como já comentado, o módulo de IOException
é importante para lançar uma exceção. No Java, uma Exception é uma classe que representa uma condição excepcional que ocorre durante a execução de um programa. Ao ocorrer um erro, uma instância de uma classe de Exception é criada e lançada, interrompendo a execução normal do programa. As Exceptions permitem que os programas lidem com erros e situações excepcionais de forma mais controlada.
Por isso, faremos uma pequena alteração no código fonte do servidor utilizando a palavra throws
após a assinatura do nosso entry point. O throws
é uma declaração que informa que um método pode lançar uma exceção durante a sua execução.
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
public class Main {
public static void main(String[] args) throws IOException {
String greeting = "Hello, World!";
System.out.println(greeting);
}
}
Como próximo passo, devemos criar uma instância que representa o endereço e a porta em que o servidor deve ser executado. Para isso utilizaremos a classe InetSocketAddress
. A classe InetSocketAddress
possui dois construtores que podem ser utilizados para instanciar um objeto. O primeiro deles recebe um objeto do tipo InetAddress
e um número de porta, e o segundo recebe uma string com o endereço IP ou nome do host e um número de porta.
Aqui está uma explicação mais detalhada de cada um dos parâmetros:
-
InetAddress
: é um objeto que representa um endereço IP. Ele pode ser obtido através da classeInetAddress
do Java. Ao utilizar esse parâmetro, estamos especificando o endereço IP do servidor ou cliente que será utilizado na conexão. -
String
: é uma string que representa o endereço IP ou nome do host. Ele é útil quando queremos criar uma instância deInetSocketAddress
a partir de uma string. Por exemplo, se quisermos criar um endereço a partir de uma string "localhost", podemos passar essa string como parâmetro. -
int
: é um número inteiro que representa a porta que será utilizada na conexão. A porta é utilizada para especificar o canal de comunicação que será utilizado pelo servidor ou cliente.
Ao instanciar um objeto de InetSocketAddress
, precisamos passar esses parâmetros no construtor de acordo com o que desejamos.
Para deixar mais didático, declarei uma variável do tipo String
com o identificador host
e uma variável do tipo int
com o identificador port
, além de alterar nossa antiga variável chamada greetings
para response
.
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
public class Main {
public static void main(String[] args) throws IOException {
int port = 8080;
String host = "localhost";
String response = "Hello, World!";
InetSocketAddress address = new InetSocketAddress(host, port)
System.out.println(response);
}
}
Por fim a cereja do nosso bolo, a classe responsável por criar o servidor HTTP. A classe HttpServer
, que pode ser usada para criar um servidor HTTP que ouve em uma porta específica e manipula solicitações HTTP de entrada,
A classe HttpServer
fornece um método estático create()
que cria uma instância do servidor HTTP. O método create()
aceita dois parâmetros: um InetSocketAddress
e um int
que indica o número máximo de solicitações que o servidor pode manipular simultaneamente. Quando o servidor recebe uma solicitação, ele cria uma nova thread para manipular a solicitação e liberar o thread principal para lidar com novas solicitações.
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
public class Main {
public static void main(String[] args) throws IOException {
int port = 8080;
String host = "localhost";
String response = "Hello, World!";
InetSocketAddress address = new InetSocketAddress(host, port)
HttpServer server = HttpServer.create(address, 0);
System.out.println(response);
}
}
Agora precisamos criar um contexto para o servidor HTTP, indicando o que deve acontecer quando o servidor receber uma requisição para a rota "/" (root). No contexto de um servidor HTTP, o termo "contexto" se refere a um mapeamento entre uma determinada URL e uma função ou método que deve ser executado para lidar com as requisições feitas para essa URL.
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
public class Main {
public static void main(String[] args) throws IOException {
int port = 8080;
String host = "localhost";
String response = "Hello, World!";
InetSocketAddress address = new InetSocketAddress(host, port)
HttpServer server = HttpServer.create(address, 0);
server.createContext("/", (exchange -> {}));
System.out.println(response);
}
}
server.createContext("/", (exchange -> {...} cria um contexto para a URL raiz ("/") e define uma função lambda para lidar com as requisições feitas para essa URL. O conceito de lambda no Java é uma expressão que representa uma função anônima, que pode ser passada como argumento para um método ou atribuída a uma variável.
A palavra-chave exchange
representa a troca de informações entre o cliente e o servidor. A função lambda recebe esse objeto exchange
como parâmetro e, a partir dele, é possível acessar e manipular os dados da requisição e da resposta.
Com o contexto criado precisamos aplicar algumas configurações para definir o formato do conteúdo da resposta.
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
public class Main {
public static void main(String[] args) throws IOException {
int port = 8080;
String host = "localhost";
String response = "Hello, World!";
InetSocketAddress address = new InetSocketAddress(host, port)
HttpServer server = HttpServer.create(address, 0);
server.createContext("/", (exchange -> {
exchange.getResponseHeaders().set("Content-Type", "text/plain");
exchange.sendResponseHeaders(200, response.getBytes().length);
exchange.getResponseBody().write(response.getBytes());
exchange.close();
}));
System.out.println(response);
}
}
Dentro da função lambda, primeiro é definido o tipo do conteúdo da resposta, usando o método set()
do objeto getResponseHeaders()
.
Em seguida, o código envia a resposta ao cliente, indicando o código de status 200 (OK), o tamanho da resposta e o conteúdo em si, usando os métodos sendResponseHeaders()
, getResponseBody()
e write()
do objeto exchange
.
Por fim, o método close()
é chamado para encerrar a troca de informações entre o cliente e o servidor.
Ao final, precisamos inicializar nosso servidor web. O método server.start()
é responsável por iniciar o servidor HTTP. Após a chamada desse método, o servidor começa a ouvir as solicitações HTTP recebidas na porta e host especificados.
A última linha, System.out.println("Servidor iniciado em http://localhost:8080/");
, exibe uma mensagem no console informando ao usuário que o servidor foi iniciado com sucesso e em qual URL ele está sendo executado. Essa mensagem é apenas informativa e não interfere no funcionamento do servidor. É uma forma de indicar ao usuário que o servidor está pronto para receber as solicitações.
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
public class Main {
public static void main(String[] args) throws IOException {
int port = 8080;
String host = "localhost";
String response = "Hello, World!";
InetSocketAddress address = new InetSocketAddress(host, port)
HttpServer server = HttpServer.create(address, 0);
server.createContext("/", (exchange -> {
exchange.getResponseHeaders().set("Content-Type", "text/plain");
exchange.sendResponseHeaders(200, response.getBytes().length);
exchange.getResponseBody().write(response.getBytes());
exchange.close();
}));
server.start();
System.out.println("Servidor iniciado em http://" + host + ":" + port + "/");
}
}
Considerações finais
Em resumo, esse projeto cria um servidor HTTP simples na porta 8080 e no host "localhost". Quando uma solicitação HTTP get é feita para a URL raiz "/", o servidor responde com o texto "Hello
World!".
O método main() é responsável por inicializar o servidor e definir o contexto para a URL raiz "/". Dentro do contexto, é definida uma função lambda que trata a solicitação HTTP recebida. Nessa função, é definido o tipo de conteúdo da resposta como "text/plain", o código de status da resposta como 200 (OK), o corpo da resposta como o texto "Hello World!" e, por fim, a resposta é enviada para o cliente e a conexão é fechada.
Por fim, o servidor é iniciado e uma mensagem é exibida no console informando a URL em que o servidor está sendo executado.
Neste artigo, vimos como criar um servidor web Java simples sem o uso de nenhum framework. Com apenas algumas linhas de código, é possível criar um servidor que recebe solicitações HTTP e responde com o conteúdo desejado.
Embora seja possível criar um servidor web Java sem framework, é importante lembrar que frameworks como Spring e Spark oferecem muitas funcionalidades úteis que podem acelerar o desenvolvimento de um aplicativo web.
Se você quiser ver o código completo do servidor web Java que criamos neste artigo, confira o meu projeto no GitHub: pure-java-server.
Espero que este artigo tenha sido útil e que você tenha aprendido algo novo sobre como criar um servidor web Java simples. Fique à vontade para compartilhar suas ideias e comentários abaixo. Até a próxima! 👋
Posted on April 20, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.