README.md 8.39 KB
Newer Older
Nelso Jost's avatar
Nelso Jost committed
1
# emm-webapp
2

3
4
5
Aplicação web dedicada ao armazenamento de dados meteorológicos do projeto [Estações Meteorológicas Modulares](http://cta.if.ufrgs.br/projects/estacao-meteorologica-modular/wiki/Wiki) (EMM).

Atualmente publicada em:
Nelso Jost's avatar
Nelso Jost committed
6
* [dados.cta.if.ufrgs.br/emm](http://dados.cta.if.ufrgs.br/emm/)
7

8
9
10
11
12
13
14
## API

A webapp oferece uma RESTful API para envio e acesso aos dados armazenados. Funciona assim: envia-se uma requisição HTTP contendo um objeto JSON (header `content-type: application/json`) para a URL; a webapp responde com outro objeto JSON. 

> Note: Objetos JSON são análogos (em geral) aos dicionários de Python. 

* No advento de erros, o seguinte JSON será retornado:
Nelso Jost's avatar
Nelso Jost committed
15
  * `{"Error": message}`
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

### /get/boardhash (POST)

Permite ao usuário obter a *hash* de autenticação da sua própria estação. Esta *hash* (string de 128 caracteres) deve ser utilizada para autenticar o envio de dados pela API.

* ENTRADA:
  ```
  {
      "board_id": int,
      "user_password": str
  }
  ```

* SAÍDA:
  ```
  {
      "board_hash": str
  }
  ```

### /post/rawsensordata (POST)

Permite ao usuário enviar dados para sua estação.  

Naturalmente, a tarefa pode ser automatizada por um data logger, tal como [emm-logger](https://git.cta.if.ufrgs.br/meteorolog/logger).

* ENTRADA:
  ```
  {
      "board_hash": str,               # hash de autenticação de usuário
      "data":                          # dados a serem guardados
       [
            {
                "datetime":            # chave primária da tabela
                {
                    "value": str,      # ex: 2016-08-10-17-20-00
                    "format": str,     # ex: %Y-%m-%d-%H-%M-%S
                    "source": str      # ex: "RTC", "WEB", "SYS"
                },
                "sensors":             # N sensores lidos
                {
                    "name1": number, "name2": number, ..., "nameN": number
                }
            }
       ]
  }
  ```

* SAÍDA:
  ```
  {
      "success": "{N} new points were saved on the board."
  }
  ```

### /get/csv/rawsensordata/<int:board_id>

Permite a qualquer pessoa obter os dados da estação identificada por `<int:board_id>`. 
Nelso Jost's avatar
Nelso Jost committed
74

75
* Retorna um arquivo `.csv` contendo TODOS os dados da estação, no formato:
Nelso Jost's avatar
Nelso Jost committed
76
77
78
  ```
  DATETIME,sensor1,sensor2,...,sensorN
  ```
79
80
81
82
83
84
85
86

Esta é uma requisição GET simples (não requer JSON) e pode ser feita em um web browser quaquer.

#### Utilizando a API RESTful

Há duas opções básicas para interagir com a API:

* **Curl**
Nelso Jost's avatar
Nelso Jost committed
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
    ```text
    $ curl -i -H 'content-type: application/json' -X POST 
           -d  '{"board_id": 3,"user_password": "1234"}' 
           http://dados.cta.if.ufrgs.br/emm/api/get/boardhash
    
    HTTP/1.1 200 OK
    Server: nginx/1.6.2
    Date: Thu, 25 Jun 2011 21:45:12 GMT
    Content-Type: application/json
    Content-Length: 88
    Connection: keep-alive
    Set-Cookie: session=eyJfaWQiOiJhMjUwZjVhMDViMjdkMGNkOTUzN2Q4YTViYWQzODFiMiJ9.CqDqWA.5TmPTa9zus8ZVo0avVnwz-O4-Dc; Domain=.dados.cta.if.ufrgs.br; HttpOnly; Path=/
    
    {
      "board_hash": "pbkdf2:sha1:1000$rT98ercf$bf77e6ca081826cbf9fc0fab23cb93bf17fe598b"
    }
    ```
104
105
106

* Python + excelente biblioteca **requests** (instale via `pip3`).

Nelso Jost's avatar
Nelso Jost committed
107
108
109
110
111
112
113
114
115
116
    ```text
    >>> import requests
    >>> url = 'http://dados.cta.if.ufrgs.br/emm/api/get/boardhash'
    >>> d = {'board_id': 3, 'user_password': '1234'}
    >>> r = requests.post(url, json=d)
    >>>> r
    <Response [200]>          # 200 significa "sucesso"
    >>> r.json()
    {'board_hash': 'pbkdf2:sha1:1000$rT98ercf$bf77e6ca081826cbf9fc0fab23cb93bf17fe598b'}
    ```
117
118

## Instalação e manutenção
Nelso Jost's avatar
Nelso Jost committed
119

120
Seguem abaixo instruções para instalação e manutenção da webapp em seu próprio servidor. 
Nelso Jost's avatar
Nelso Jost committed
121

122
O projeto inclui um arquivo `Makefile` contendo diversos comandos simples para manutenção no diretório raiz. É preciso estar neste diretório para executar:
123
124

```
125
~/emm-webapp $ make <command>
126
```
127
Execute simplesmente `make` ou `make help` para obter uma lista de todos os comandos disponíveis e uma breve descrição. 
128

Nelso Jost's avatar
Nelso Jost committed
129
### 1. Preparando ambiente de execução
130

Nelso Jost's avatar
Nelso Jost committed
131
* **1.1** Obtenha uma cópia do repositório atual:
132
```
Nelso Jost's avatar
Nelso Jost committed
133
134
$ git clone https://git.cta.if.ufrgs.br/meteorolog/emm-webapp.git
$ cd emm-webapp
135
```
136

Nelso Jost's avatar
Nelso Jost committed
137
* **1.2** Instale os programas necessários (requer permissão de root):
138
```
Nelso Jost's avatar
Nelso Jost committed
139
$ make deb-req
140
```
Nelso Jost's avatar
Nelso Jost committed
141
142
143
144
145
146
147
148
Os seguintes programas serão instalados no sistema via apt-get (logo, apenas para distribuições Debian):
 * **nginx**: Servidor web, utilizado para reverse proxy;
 * **supervisor**: Gerenciador de processos em background; 
 * **python3**: Interpretador da linguagem na qual a web app foi escrita;
 * **python3-pip**: Gerenciador de pacotes do Python;
 * **virtualenv**: Permite a criação de um ambiente virtual contendo uma instalação isolada de Python e das dependencias do projeto (ver arquivo `requirements.pip`);

* **1.3** Crie o ambiente virtual de Python onde a web app será executada:
149
```
Nelso Jost's avatar
Nelso Jost committed
150
$ make venv
151
```
152

Nelso Jost's avatar
Nelso Jost committed
153
* **1.4** Exporte uma frase-chave para ser usada como token de segurança da web app:
154
```
Nelso Jost's avatar
Nelso Jost committed
155
$  export EMMPASS=<DigiteUmaFraseSemEspaços>
156
```
Nelso Jost's avatar
Nelso Jost committed
157
OBS: dê um espaço em branco depois do `$` antes de digitar o comando `export` para que o texto não seja gravado no histórico do terminal!
158

Nelso Jost's avatar
Nelso Jost committed
159
### 2. Preparando o banco de dados
160

Nelso Jost's avatar
Nelso Jost committed
161
É possível trabalhar em modo desenvolvimento ou produção configurando a variável ambiente `EMMCONFIG`.
162

Nelso Jost's avatar
Nelso Jost committed
163
#### 2.1 Desenvolvimento: SQLite, arquivo local
164

Nelso Jost's avatar
Nelso Jost committed
165
166
167
```
$ export EMMCONFIG=dev
$ make initdb
168
169
```

Nelso Jost's avatar
Nelso Jost committed
170
#### 2.2 Produção: servidor MySQL
Nelso Jost's avatar
Nelso Jost committed
171

Nelso Jost's avatar
Nelso Jost committed
172
O `Makefile` proporciona vários comandos para execução de scripts MySQL (ver `make help` e `production/manage_mysql.py` para maiores detalhes). 
173

Nelso Jost's avatar
Nelso Jost committed
174
Necessário apenas uma vez, o comando seguinte cria o usuário `ctaemm` e o banco `emmdb` no servidor:
175
```
Nelso Jost's avatar
Nelso Jost committed
176
$ make mysql-setup
177
```
Nelso Jost's avatar
Nelso Jost committed
178
OBS: Nesse o ponto o banco ainda está vazio (sem tabelas).
179

180
```
Nelso Jost's avatar
Nelso Jost committed
181
182
$ export EMMCONFIG=prod
$ make initdb
183
```
Nelso Jost's avatar
Nelso Jost committed
184
OBS: As tabelas serão recriadas e dados existentes serão perdidos!
185

Nelso Jost's avatar
Nelso Jost committed
186
### 3. Executando a web app
187

Nelso Jost's avatar
Nelso Jost committed
188
É possível executar a aplicação em modo desenvolvimento ou produção. Em ambos os casos, é um servidor WSGI rodando em uma porta específica que intermedia as requisições web (GET, POST, etc) com a aplicação Python. 
189

Nelso Jost's avatar
Nelso Jost committed
190
A porta pode (e deve) ser configurada na variável ambiente `EMMPORT`. Padrão: 5000.
191

Nelso Jost's avatar
Nelso Jost committed
192
#### 3.1 Desenvolvimento
193

Nelso Jost's avatar
Nelso Jost committed
194
195
196
197
* Execução em primeiro plano;
* Servidor WSGI: Werkzeug (padrão integrado ao Flask);
* Flag `DEBUG=True` (brecha de segurança!);
* Banco de dados local SQLite;
198

Nelso Jost's avatar
Nelso Jost committed
199
Execute fazendo:
200
```
Nelso Jost's avatar
Nelso Jost committed
201
$ make run
202
203
```

Nelso Jost's avatar
Nelso Jost committed
204
205
206
207
208
209
210
211
212
213
214
215
216
Publicado por padrão em: `http://localhost:5000`. 
Pressione CTRL+C para interromper a execução.

#### 3.2 Produção

* Execução em plano de fundo;
* Servidor WSGI: Gunicorn (instalado via pip em `make venv`);
* Flag `DEBUG=False`;
* Servidor de banco de dados MySQL;

Requer variável ambiente `EMMSERVER` contendo o nome do servidor de rede da máquina. 

A publicação é feita com:
217
```
Nelso Jost's avatar
Nelso Jost committed
218
$ make deploy
219
```
220

Nelso Jost's avatar
Nelso Jost committed
221
222
Assumindo `EMMSERVER=localhost`, o site estará publicado localmente em:
* http://localhost/emm
223

Nelso Jost's avatar
Nelso Jost committed
224
225
226
227
Em resumo, o comando acima faz:
* Cria um servidor WSGI Gunicorn na porta `EMMPORT`;
* Registra o processo Gunicorn na ferramenta Supervisor para iniciação automática junto ao sistema operacional;
* Registra o site no servidor web Nginx na forma de um reverse proxy da porta 80 para a aplicação WSGI na porta `EMMPORT`.
228

Nelso Jost's avatar
Nelso Jost committed
229
A publicação pode ser desfeita com:
230
```
Nelso Jost's avatar
Nelso Jost committed
231
$ make undeploy
232
```
233

Nelso Jost's avatar
Nelso Jost committed
234
235
236
237
238
239
240
### 4. Manutenção

Independente do banco de dados utilizado, ele pode ser manipulado em alto nível graças ao mapeador objeto-relacional [SQLAlchemy](http://www.sqlalchemy.org/) utilizado pela aplicação.

O comando:
```
$ make shell
241
```
Nelso Jost's avatar
Nelso Jost committed
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
inica uma sessão IPython onde as tabelas e registros podem ser acessados e alterados como objetos Python. A exemplo:
```
>>> tables                   # para ver as tabelas disponíveis
>>> boards                   # a exemplo, esta é a tabela boards
app.models.board.Board      
>>> boards.__fields__        # lista os campos da tabela
{'description', 'id', 'latitude', 'longitude', 'nickname', 'user_id'}
>>> boards.ls                # lista todas linhas/registros da tabela 
[<Board id=1 user='admin' nickname='Pezzi' sensor_count=4>,
 <Board id=2 user='admin' nickname='CAP' sensor_count=4>]
>>> b = boards.get(2)        # pega um registro via ID
>>> b.nickname = 'UFRGS'     # altera um campo
>>> commit()                 # confirma a operação no DB
>>> boards.ls
[<Board id=1 user='admin' nickname='Pezzi' sensor_count=4>,
 <Board id=2 user='admin' nickname='UFRGS' sensor_count=4>]
Nelso Jost's avatar
Nelso Jost committed
258
```