Countdown

Final Countdown

Faltam dias. Ou segundos.

segunda-feira, 1 de junho de 2015

#24) Aplicações Web

Boa noite,

Hoje o tópico será sobre Aplicações Web.

1. Introdução (Segurança)

Vamos falar hoje sobre um dos campos mais hostis e inóspitos que existe no ramo da programação: O ambiente Web. Um ambiente fértil para novas aplicações e que representa cada vez mais o futuro do desenvolvimento de softwares, mas ao mesmo tempo traz uma série de dificuldades.

Adicionei um novo Gadget ao meu blog para demonstrar como a Web pode ser um ambiente terrível e perigoso. O Gadget mostra em tempo real o fluxo de ataques DDOS realizados ao redor do mundo. Lembrando a definição de ataque DDOS:

"A Distributed Denial of Service (DDoS) attack is an attempt to make an online service unavailable by overwhelming it with traffic from multiple sources. They target a wide variety of important resources, from banks to news websites, and present a major challenge to making sure people can publish and access important information".



Se você, leitor, se interessou pelo tema, aconselho que aprofunde um pouco mais sua visão com algum dos links da referência deste post. Sei que minha abordagem apreensiva quanto à segurança é um pouco inusitada, mas é ela que explica fatos como, por exemplo:
  •  A implementação de Applets em Java ser tão difícil e pouco estimulada devido aos perigos que uma máquina virtual (proveniente da execução do aplicativo) pode ocasionar ao seu utilizador.
  • Não há possibilidade de se utilizar diretamente serviços Sockets (explicaremos neste post) e envio de Packets empregando Javascript, o que poderia ser um desastre de segurança.
  • A enorme dificuldade de se hospedar arquivos online de forma gratuita, principalmente de aplicações que não sejam em HTML (um arquivo executável .jar, por exemplo), devido às aplicações maliciosas que viriam daí.
E estes são alguns poucos itens que enuncio aqui, dificuldades sérias para o nosso projeto Web que se originam fundamentalmente de problemas de segurança em rede.


2. Predefinição

A linguagem Java conta com uma boa API de Sockets pelo pacote java.net, que será o mais utilizado aqui neste post. Antes, vamos tomar algumas definições básicas:

Glossário

  1. Socket (soquete de rede) é o ponto final do fluxo de informação entre 2 aplicativos através de uma rede.
  2. Packet (pacote) é uma estrutura unitária de transmissão de dados em uma rede de comunicação.
  3. Protocolo (TCP - Transmission Control Protocol) é uma convenção que controla e possibilita uma conexão/ comunicação/ transferência de dados entre dois sistemas computacionais.
  4. IP (IP - Internet Protocol) pode ser definido como um protocolo de comunicação em conjunto com o TCP (TCP/IP).
  5. Endereço IP é uma identificação de um dispositivo em uma rede local ou pública. Cada endereço de domínio é convertido em um endereço IP pelo DNS (resolução de nomes).
  6. Porta, assim como o IP existe para identificar uma máquina, a porta é solução para identificar as diversas aplicações de uma máquina. É um número de 2 bytes (varia de 0 a  65535)
  7. DNS é um sistema de gerenciamento de nomes hierárquicos distribuídos para computadores, serviços ou qualquer recurso conectado à internet ou a uma rede privada.
  8. IPV4 é um cabeçalho que contém as informações a serem transmitidas segundo uma certa lógica. Seu endereçamento possui 32 bits.
  9. UDP é um protocolo simples da camada de transporte. Permite que a aplicação escreva um datagrama encapsulado num pacote IPV4 ou IPV6 e então envia ao destino, mas não garante a chegada ao destino ou não.
  10. Datagrama é uma unidade de transferência básica associada a uma rede de comutação de pacotes em que a entrega, hora de chegada e ordem não são garantidas.
  11. Domínio é um nome que serve para localizar um conjunto de computadores na internet.
  12. Servidor é uma aplicação central que espera a conexão de Clientes para a sua execução.

Blocos de Endereços Reservados
CIDR Bloco de EndereçosDescriçãoReferência
0.0.0.0/8Rede corrente (só funciona como endereço de origem)RFC 1700
10.0.0.0/8Rede PrivadaRFC 1918
14.0.0.0/8Rede PúblicaRFC 1700
39.0.0.0/8ReservadoRFC 1797
127.0.0.0/8LocalhostRFC 3330
128.0.0.0/16Reservado (IANA)RFC 3330
169.254.0.0/16ZeroconfRFC 3927
172.16.0.0/12Rede privadaRFC 1918
191.255.0.0/16Reservado (IANA)RFC 3330
192.0.2.0/24DocumentaçãoRFC 3330
192.88.99.0/24IPv6 para IPv4RFC 3068
192.168.0.0/16Rede PrivadaRFC 1918
198.18.0.0/15Teste de benchmark de redesRFC 2544
223.255.255.0/24ReservadoRFC 3330
224.0.0.0/4Multicasts (antiga rede Classe D)RFC 3171
240.0.0.0/4Reservado (antiga rede Classe E)RFC 1700
255.255.255.255Broadcast

3. Sockets (Java)

Vamos esquentar um pouco com um pequeno exemplo de aplicação Cliente - Servidor em Java, em que cliente e servidor trocam uma simples mensagem entre si:

A aplicação Servidor disponibiliza uma determinada porta e espera a conexão do Cliente, para depois realizar a leitura dos dados enviados por ele, para depois enviar uma curta mensagem. Note que há a criação de um Socket de conexão:

ServerSocket servidor = new ServerSocket( porta );

Server.java
package sockets;

import java.io.*;
import java.net.*;
import java.util.*;

public class Server 
{
    static String Mensagem = "Hi", N;
    static String IPclient, IPserver, porta;
    static Socket cliente;
    public static void main(String[] args) throws UnknownHostException, IOException 
    {
        // Campo para exibir IPserver e porta
        IPserver = InetAddress.getLocalHost().getHostAddress();
        porta = "7777";
        
        ServerSocket servidor = new ServerSocket(Integer.parseInt(porta));
        
        // Detecção de nova requisição do cliente.
        while (cliente == null) 
        {
            cliente = servidor.accept();
            IPclient = cliente.getLocalAddress().getHostAddress();
        }
            // Leitura do valor de N
            Scanner s = new Scanner(cliente.getInputStream());
            N = s.nextLine();
            System.out.println(N);

            // Escreve a mensagem codificada.
            PrintStream saida = new PrintStream(cliente.getOutputStream());
            saida.println(Mensagem);
            
            
            while(!cliente.isClosed())
        
        saida.close();        
        cliente.close();
    }

}

Para a aplicação Cliente, temos a conexão ao servidor e depois o subsequente envio envio de uma mensagem, para depois aguardar a mensagem enviada pelo cliente. Note que a conexão Socket é realizada no formato (IP, porta), conforme a sua definição:

 Socket cliente = new Socket( IP, porta );

Client.java
package sockets;

import java.io.*;
import java.net.*;
import java.util.*;

public class Client 
{
    static String Mensagem, N = "10";
    static String IPclient, IPserver, porta;
    static Socket cliente;
    public static void main(String[] args) throws UnknownHostException, IOException 
    {
        // Campo para digitar IPserver & Porta & exibir IPcliente
        IPserver = "192.168.197.1";
        porta = "7777";
        IPclient = InetAddress.getLocalHost().getHostAddress();
        
        // Conexão ao servidor
        cliente = new Socket(IPserver, Integer.parseInt(porta));
        
            // Cliente envia o valor de "n"
            PrintStream saida = new PrintStream(cliente.getOutputStream());
            saida.println(N);
            
            // Realiza a leitura da mensagem
            Scanner s = new Scanner(cliente.getInputStream());
            Mensagem = s.nextLine();
            System.out.println(Mensagem);

        saida.close();
        cliente.close();
    }
}

Como o leitor pode observar, o uso da aplicação é bem direto, sendo que o grande desafio é sincronizar o envio das mensagens usando Threads, identificar usuários que se conectam ao servidor e distribuir portas de conexão a eles, permitir diversos clientes simultâneos, etc.

Estes desafios podem ser resolvidos usando os conceitos que viemos desenvolvendo até agora no blog, de modo que deixaremos isso ao encargo do leitor.

4. WebSockets (Javascript)


Note que para usar WebSockets, será necessário criar um arquivo HTML estático como estrutura para o código dinâmico do Javascript. Fundamentalmente, Javascript não permite a utilização de Sockets diretamente como ocorre na linguagem Java, por motivos de segurança. 

No entanto, WebSockets surge como uma alternativa moderna para contornar esse problema, usando funções encapsuladas de forma a restringir o uso de Sockets e a sua implementação.

Abaixo temos um exemplo de aplicação de WebSockets:

<!DOCTYPE HTML>
<html>

   <head>
 
      <script type="text/javascript">
         function WebSocketTest()
         {
            if ("WebSocket" in window)
            {
               alert("WebSocket is supported by your Browser!");
               // Let us open a web socket
               var ws = new WebSocket("ws://localhost:9998/echo");
    
               ws.onopen = function()
               {
                  // Web Socket is connected, send data using send()
                  ws.send("Message to send");
                  alert("Message is sent...");
               };
    
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                  alert("Message is received...");
               };
    
               ws.onclose = function()
               { 
                  // websocket is closed.
                  alert("Connection is closed..."); 
               };
    
            }
            else
    
            {
               // The browser doesn't support WebSocket
               alert("WebSocket NOT supported by your Browser!");
            }
         }
      </script>
  
   </head>
 
   <body>
      <div id="sse">
         <a href="javascript:WebSocketTest()">Run WebSocket</a>
      </div>
   </body>
 
</html>

Por outro lado, devemos empregar um Servidor, como por exemplo o Glassfish, para a criação de um terminal Servidor, com um endereço do tipo:

"ws://localhost:8080/Bizuca/server"

package Servidor;

import java.io.IOException;
import java.util.ArrayList;
 
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/server") 
public class Servidor 
{   
    ArrayList<Player> Players = new ArrayList<>();
    public static int i = 0;
    public static String Msg = "";
    
    @OnOpen
    public void onOpen(Session session)
    {
        Players.add(new Player("null",session.getId()));
        try {
            session.getBasicRemote().sendText("Conexão estabelecida. Nº"+(i++));
        } catch (IOException ex) { }
    }
    
    @OnMessage
    public void onMessage(String message, Session session)
    {
        Msg = message;
            message+=": Sender : ("+session.getId()+")";
        try {
            session.getBasicRemote().sendText(message);
        } catch (IOException ex) { }
    }
 
    @OnClose
    public void onClose(Session session){
        for(Player P: Players)
            if (P.Id.equals(session.getId()))
                Players.remove(P);
    }
}

É possível inclusive colocar essa aplicação online com uso de servidor Glassfish, empregando por exemplo o serviço Layershift (versão trial de 14 dias), com permissão ao uso de banco de dados online e execução da sua aplicação em um ambiente online.

Ambiente Layershift para Upload de projetos.


Porém sempre existem alternativas, como o emprego da API Sockets.io, que permite o uso de Sockets de maneira simplificada, ou da API jsocket, desenvolvida no MIT, que emprega arquivos Flash para burlar o sistema de segurança agressivo de Javascript.

5. Programando em baixo nível

Somente para ilustrar um pouco mais os conceitos abordados neste tópico, temos o seguinte exemplo de aplicação de Datagramas (a unidade básica de transferência de dados) em um chat, permitindo vislumbrar em baixo nível como é realizada a troca de informações na Web:

A classe Conexão realiza a conexão dos usuários e também se encarrega da interpretação e da troca de mensagens, convertendo Datagrams em mensagens e vice-e-versa:

package online;
import java.io.IOException;
import java.util.logging.*;
import java.net.*;
import java.util.*;

public class Conexao extends Observable 
{
    private String ip, mensagem;
    private int porta;

    public void SetConexao(String ip, int porta) {
        this.ip = ip; this.porta = porta;
        new Thread(new Recebe()).start();
    }
    public String getIP () {
        try {
            return ""+InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException ex) {
            Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex);
        }
        return "";
    }
    public String getMensagem() { return mensagem; }
    public String getIp() { return ip; }
    public int getPorta() {  return porta;  }
    public void envia(String texto) {  new Thread(new Envia(texto)).start();  }

    public void notifica(String mensagem) {
        this.mensagem = mensagem;
        setChanged();
        notifyObservers();
    }

    class Recebe implements Runnable 
    {
        byte[] dadosReceber = new byte[255];
        boolean erro = false;
        DatagramSocket socket = null;

        @Override
        public void run() {
            while (true) {
                try {
                    socket = new DatagramSocket(getPorta());
                } catch (SocketException ex) {
                    Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex);
                }
                erro = false;
                while (!erro) {
                    DatagramPacket pacoteRecebido = new DatagramPacket(dadosReceber, dadosReceber.length);
                    try {
                        socket.receive(pacoteRecebido);
                        byte[] b = pacoteRecebido.getData();
                        String s = "";
                        for (int i = 0; i < b.length; i++)
                            if (b[i] != 0)
                                s += (char) b[i];
                        String nome = pacoteRecebido.getAddress().toString() + " disse:";
                        notifica(nome + s);
                    } catch (Exception e) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException ex) {
                            Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex);
                        }
                        erro = true;
                        continue;
                    }
                }
            }
        }
    }

    class Envia implements Runnable 
    {
        String texto;
        public Envia(String texto) {
            this.texto = texto;
        }
        @Override
        public void run() {
            byte[] dados = texto.getBytes();

            try {
                DatagramSocket clientSocket = new DatagramSocket();
                InetAddress addr = InetAddress.getByName(getIp());
                DatagramPacket pacote = new DatagramPacket(dados, dados.length, addr, getPorta());
                clientSocket.send(pacote);
                clientSocket.close();
            } catch (SocketException ex) {
                Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex);
            } catch (UnknownHostException ex) {
                Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

Interface gráfica, responsável por capturar as mensagens e exibi-las, além de gerenciar as ferramentas usadas pelo usuário:

package online;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Observable;
import java.util.Observer;

public class GUI extends JFrame implements Observer 
{
    int xMouse, yMouse;
    private Conexao conex;
    private JTextField textoIP, textoPorta;
    private JTextArea chatjTextArea, mensagemjTextArea;     
    private JButton enviarjButton, connect;
    private JScrollPane jScrollPane1, jScrollPane2; 
    private JLabel credito, IP, porta, meuIP;
    
    public GUI (Conexao conexao)
    {
        conex = conexao;
        conex.addObserver(this);
        initComponents();
        initEvents();
        setBounds(250,150,600,550);
        escreve("Chat iniciado com " + conex.getIp() + ":" + conex.getPorta());
        meuIP.setText("Meu IP: "+conex.getIP());
    }
    private void initComponents()
    {
        setTitle("Xhat");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        getContentPane().setBackground(new Color(0, 0, 0));
        setLocationRelativeTo(null); setLayout(null); setVisible(true);
        
        IP = new JLabel("IP:"); IP.setForeground(Color.white); IP.setBounds(50,50,100,20); add(IP);
        porta = new JLabel("Porta:"); porta.setForeground(Color.white); porta.setBounds(200,50,100,20); add(porta);
        meuIP = new JLabel(); meuIP.setForeground(Color.white); meuIP.setBounds(200,20,150,20); add(meuIP);
        
        textoIP = new JTextField("192.168.0."); textoIP.setBounds(70,50,100,25);
        textoIP.setBackground(Color.black); textoIP.setForeground(Color.white); add(textoIP);
        textoPorta = new JTextField("5000"); textoPorta.setBounds(250,50,100,25);
        textoPorta.setBackground(Color.black); textoPorta.setForeground(Color.white); add(textoPorta);
        
        chatjTextArea = new JTextArea();  chatjTextArea.setEditable(false);
        chatjTextArea.setForeground(Color.white); chatjTextArea.setBackground(Color.black);
        jScrollPane1 = new JScrollPane(); jScrollPane1.setBounds(50,100,450,300); add(jScrollPane1);
        jScrollPane1.setViewportView(chatjTextArea);
        
        mensagemjTextArea = new JTextArea(); mensagemjTextArea.requestFocusInWindow();
        mensagemjTextArea.setForeground(Color.white); mensagemjTextArea.setBackground(Color.black);
        jScrollPane2 = new JScrollPane(); jScrollPane2.setBounds(50,400,350,50); add(jScrollPane2);
        jScrollPane2.setViewportView(mensagemjTextArea);
        
        connect = new JButton("Conectar"); 
        connect.setBackground(Color.black); connect.setForeground(Color.white);
        connect.setBounds(400,50,100,30); add(connect);
        
        enviarjButton = new JButton("Enviar"); 
        enviarjButton.setBackground(Color.black); enviarjButton.setForeground(Color.white);
        enviarjButton.setBounds(400,400,100,50); add(enviarjButton);

        credito = new JLabel("By Tuyama"); credito.setForeground(Color.white);
        credito.setBounds(250,475,100,20); add(credito);

        pack();
    }
    
    private void initEvents()
    {
        mensagemjTextArea.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyReleased(java.awt.event.KeyEvent evt) {
                if (evt.getKeyCode() == KeyEvent.VK_ENTER)
                    envia();
            }
        });
        enviarjButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                envia();
            }
        });
        connect.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                conex.SetConexao(textoIP.getText(),Integer.parseInt(textoPorta.getText()));
                chatjTextArea.setText("");
                escreve("Chat iniciado com " + conex.getIp() + ":" + conex.getPorta());
            }
        });
        addMouseMotionListener(new MouseMotionListener() {
            public void mouseDragged(MouseEvent e) { }
            public void mouseMoved(MouseEvent e) {
                xMouse = e.getX();
                yMouse = e.getY();
            }
        });
    }
    
    private void envia(){
        if (mensagemjTextArea.getText().equals("\n"))
            conex.envia("&"+xMouse+"@"+yMouse);
        if (!mensagemjTextArea.getText().isEmpty()) {
            conex.envia(mensagemjTextArea.getText());
            escreve("Você disse: "+mensagemjTextArea.getText());
            mensagemjTextArea.setText("");
        }
    }
    private void escreve(String texto){
        chatjTextArea.append(texto+"\n");
         if (!chatjTextArea.getText().isEmpty() && !chatjTextArea.isFocusOwner())
                chatjTextArea.setCaretPosition(chatjTextArea.getText().length() - 1);
    }
    @Override
    public void update(Observable o, Object arg) {
        String Msg = conex.getMensagem();
        int MouseX, MouseY, S1, S2;
        if (!Msg.contains("&")&&!Msg.contains("@"))
            escreve(Msg);
        else {
            for (S1=0; Msg.charAt(S1)!='&'; S1++);
            for (S2=0; Msg.charAt(S2)!='@'; S2++);
            MouseX = Integer.parseInt(Msg.substring(S1+1, S2));
            MouseY = Integer.parseInt(Msg.substring(S2+1));
            credito.setBounds(MouseX,MouseY,100,20);
        }
    }
}

Observe que a estrutura deste programa usa um conceito de Design Patterns (Observable & Observer) permitindo sincronizar o envio e recibo das mensagens, permitindo a troca fluente de mensagens entre as partes envolvidas.

6. Conclusão

A programação de Aplicações Web é complexa e promissora. Seria possível escrever cursos inteiros sobre suas aplicações e conceitos, aprofundar no tema de segurança e estudar mais a fundo a dinâmica de troca de informações.

Mas creio que o pequeno post de hoje deu uma pequena abordagem do tema ao nosso leitor, que agora pode se considerar iniciado no tema.

7. Referência:

(Ataque DDOS): 

(Sockets):

(Sockets & Javascript):

(Glassfish - Web Sockets (Javascript))

(jSocket - Javascript)

(Socket.io - Alternativa)

Nenhum comentário:

Postar um comentário