-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchapter10.txt
More file actions
692 lines (499 loc) · 44.1 KB
/
chapter10.txt
File metadata and controls
692 lines (499 loc) · 44.1 KB
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
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
{:: encoding="UTF-8" /}
# Aplicaciones Web
## INTRODUCCIÓN A PYTHON EN LA WEB
Ya vimos cómo ejecutar programas localmente, en este capítulo mostraremos cómo portarlos a la web.
La principal ventaja de hacer que un programa esté disponible en la web es que puede llegar a más usuarios sin la necesidad de instalar una copia del programa ni tener una instalación de Python. A veces, el programa accede a recursos exigentes como grandes bases de datos que no pueden instalarse en el disco rígido del usuario final.
Para hacer aplicaciones web se necesitan otras herramientas además de Python, como HTML, CSS, JS, administración de servidores web, entre otras. Esos temas están más allá del alcance de este libro, por lo que les recomiendo que los lean si nunca antes han diseñado una página web. Conocer los conceptos básicos de HTML tiene una importancia especial, ya que la mayoría de los laboratorios de IT tienen personal dedicado a la configuración y el mantenimiento de los servidores web, pero el diseño HTML es algo que rara vez harán por nosotros. Para obtener más información sobre HTML, consultá la sección "Recursos adicionales" al final del capítulo. Con respecto al servidor web, Python proporciona uno que es útil para el desarrollo y las pruebas, pero no para su uso en producción. En este caso, debe usar un programa de servidor independiente como **Apache** o **Nginx**. Dado que **Apache** es el más popular, este libro cubrirá cómo configurarlo. Es común que tu institución proporcione el servidor web, ahora también es común que el departamento de IT o sistemas proporcione una máquina virtual en la que debe instalar todo el software requerido en lugar de solo tu aplicación.
Hay varias formas de usar Python en un servidor web, como **CGI** (**C**ommon **G**ateway **I**nterface), **mod_python** y **WSGI** (**W**eb **S**erver **G**ateway **I**nterface). **CGI** es el método más antiguo para ejecutar contenido dinámico en una página web. Los primeros servidores web solo mostraban HTML estático, hasta que se definió el protocolo **CGI** en 1993. En la actualidad se sigue utilizando y algunas compañías de alojamiento incluso ofrecen CGI como la única opción para hacer servidores web interactivos. Como ventaja es la más fácil de configurar y está disponible en casi todos los servidores web sin tener que instalar software adicional. Esencialmente es un protocolo para conectar una aplicación, escrita en cualquier lenguaje, con un servidor web. **mod_python** en particular consiste en un Módulo de Apache que integra Python con el servidor web. La ventaja de este enfoque es la ejecución rápida del script, dado que el intérprete de Python está cargado con el servidor web. WSGI, a su vez, es una "especificación para que los servidores web y los servidores de aplicaciones se comuniquen con las aplicaciones web". Dado que es una especificación existen varias implementaciones. La principal ventaja de WSGI es que una aplicación WSGI puede ser puesta en producción en cualquier servidor compatible con WSGI[^nota10-1] (o incluso utilizando el servidor web proporcionado por Python). Al igual que en `mod_python`, la velocidad de ejecución de los programas basados en WSGI es mejor que CGI porque no hay overhead para iniciar el intérprete de Python en cada pedido.
[^nota10-1]: Para una comparación de los servidores web WSGI, consulte este artículo: <https://www.digitalocean>.
## CGI EN PYTHON
Para esta sección, asumo que ya tenés un servidor web Apache en ejecución. Si no, puede instalarse en distribuciones Linux basadas en Debian / Ubuntu con:
{line-numbers=off}
```
$ sudo apt-get install apache2
```
Como alternativa, podés contratar cualquier plan de alojamiento web (web hosting); la mayoría de ellos tienen preinstalado Apache y CGI.
### Configuración de un servidor web para CGI
En el archivo de configuración del servidor[^nota10-2] debe haber especificaciones de que los scripts se pueden ejecutar a través de CGI, en qué directorios y cómo se llamarán.
Si los scripts están en `/var/www/apache2-default/cgi-bin` debemos incluir las siguientes líneas en el archivo de configuración del servidor.
[^nota10-2]: En el servidor web de Apache, en la mayoría de los casos, el archivo de configuración es *httpd.conf* ó *apache2.conf* y se encuentra en el directorio */etc/apache2*. Esto puede cambiar en cada instalación.
{line-numbers=off,lang=text}
```
<Directory /var/www/apache2-default/cgi-bin>
Options +ExecCGI
</Directory>
```
Agregá la siguiente línea en el archivo de configuración para especificar que los scripts ejecutables son aquellos que tienen la extensión del archivo `.py`.
{line-numbers=off}
```
AddHandler cgi-script .py
```
Si el archivo de configuración ya tiene una línea con las extensiones de archivo registradas, solo se necesita agregarle `.py`.
{line-numbers=off}
```
AddHandler cgi-script .cgi .pl .py
```
Finalmente tenemos que configurar la variable `ScriptAlias`. Requiere el path que el usuario verá en la URL y el path donde se almacenan los scripts.
{line-numbers=off}
```
ScriptAlias /cgi-bin/ /var/www/apache2-default/cgi-bin/
```
Esto es todo lo que hay en el archivo de configuración del servidor. Lo único que queda por hacer es asegurarse de que la secuencia de comandos tengan los permisos de usuario de Apache. Desde la terminal del servidor ingresá:
{line-numbers=off,lang=text}
```
$ chmod a+x MyScript.py
```
Si solo tiene acceso a FTP, use un cliente FTP para establecer los permisos.
### Probando el servidor con nuestro script
El siguiente código se puede usar para confirmar que el servidor está listo para ejecutar programas CGI:
**Listado 10.1:** `firstcgi.py`: Primer script CGI
```
#!/usr/bin/env python
print("Content-Type: text/html\n")
print("<html><head><title>Test page</title></head><body>")
print("<h1>HELLO WORLD!</h1>")
print("</body></html>")
```
**Explicación del código**: la primera línea indica la ubicación del intérprete de Python. Generalmente, esta línea es opcional y se agrega solo cuando queremos ejecutar el script directamente sin tener que llamar primero al intérprete de Python. Para los programas CGI esta línea es obligatoria.[^nota10-3] La segunda línea es importante para que el servidor web sepa que se le enviará una página HTML. Tenemos que enviar el string `Content-Type/html` seguido de dos retornos de carro. Aunque en la línea 2 solo hay un retorno de carro implícito (\n), el comando print agrega el otro. El resto del programa es similar a los otros que hemos hecho hasta este momento con la diferencia es que en este caso imprimimos el código HTML para que lo lea el navegador.
Si cargamos este programa en un servidor web y luego accedemos a la página con nuestro navegador, los resultados que veremos serán similares a los de la Figura 10.2. Si todo va bien no veremos el contenido del archivo, sino el producto de su ejecución en el servidor. Este producto (una página HTML) será procesado por el navegador web (ver Figura 10.1).
[^nota10-3]: Si no conoce la ruta al intérprete de Python pídale al administrador del sistema que instale su script. Otra opción, si tiene acceso a la línea de comando del servidor, es ejecutar whereis python.

Hay que tener en cuenta que para probar nuestras páginas necesitamos que las procese un servidor web y no las abra directamente desde nuestro disco rígido. En este caso, tendremos como resultado lo que puede verse en la Figura 10.2, en lugar de la página representada por el navegador web.
**Enviando datos a un programa CGI**
El programa anterior no es muy útil, es solo una página estática que no acepta ningún parámetro del usuario. Veamos un ejemplo de un formulario HTML minimalista que envía datos a un programa de Python que usará estos datos.
El primer paso es diseñar el formulario. En este caso, crearemos un formulario simple con un campo y se guardará como **greeting.html**:

**Listado 10.2:** `greeting.html`: Front end HTML para enviar información al programa CGI.
{line-numbers=on,lang=html}
```
<html><head><title>Very Simple Form</title></head>
<body>
<form action='cgi-bin/greeting.py' method='post'>
Your name: <input type='text' name='username'> <p>
<input type='submit' value='Send'>
6 </form></body></html>
```
**Explicación del código**: Hay dos características importantes a tener en cuenta en este pequeño formulario. La línea 3 especifica donde se encuentra el programa que va a procesar los datos (`cgi-bin/greeting.py`). En la línea 4 está el campo que el usuario debe completar (tipo "text") con una variable asociada (`username`). Este nombre de variable es importante porque la información ingresada por el usuario estará vinculada a este nombre. El formulario se parece a la de la Figura 10.3.

Veamos cómo escribir el código que aceptará los datos enviados por el formulario y a partir de él se construirá una página web en el momento.
**Listado 10.3:** `greeting.py`: El programa CGI que procesa el formulario en greeting.html.
```
#!/usr/bin/env python
import cgi
print("Content-Type: text/html\n")
form = cgi.FieldStorage()
name = form.getvalue("username","NN")[:10]
print("<html><head><title>A CGI script</title></head>")
print("<body><h2>Hello {}</h2></body></html>".format(name))
```
**Explicación del código**: en la línea 4 creamos una instancia (**form**) de la clase `cgi.FieldStorage`. Esta clase toma los valores enviados por el formulario y los hace accesibles de forma similar a un diccionario. En la siguiente línea (5) accedemos a los datos enviados por el formulario y también recortamos el número de caracteres que vamos a pasar a la función **print**; esto se hace para mitigar un potencial problema de seguridad.[^nota10-4] El método `getvalue` toma como argumento necesario, el nombre del campo a cuyo contenido queremos acceder. El segundo argumento es opcional e indica qué valor se devolverá en caso de que el campo deseado esté en blanco. Esto es similar al método **get** de los diccionarios. Desde la línea 6 en adelante, el programa imprime el código HTML utilizando el contenido de la variable. Este es el código que se renderizará en el navegador.
En resumen, utilizamos el formulario web del Listado 10.2 para ingresar un nombre y presionar "Send". Esto envía los datos y luego el programa lo lee gracias a la clase `cgi.FieldStorage` y se lo referencia como un nombre de variable que se usa en el programa para generar una página web. Vea la salida en la Figura 10.4.
[^nota10-4]: Para obtener más información sobre la protección de sitios web, ver la página 232.

### Programa web para calcular la carga neta de una proteína (versión CGI)
Al código del Listado 4.14 podemos adaptarlo fácilmente para usarlo desde una página web. Como primer paso debemos diseñar un formulario en el que un usuario pueda ingresar los datos. Esta es una propuesta de formulario:
**Listado 10.4:** `protcharge.html`: Front end HTML que envia la información al programa CGI.
{line-numbers=on,lang=html}
```
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8">
<title>Protein Charge Calculator</title>
<link href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body style="background-color:#e7f5f5;">
<div class="container"><h2>Protein Charge Calculator</h2>
<form action='/cgi-bin/protcharge.py' method='post'>
<div class="row">
<div class="col-sm-8">
<div class="form-group">
<label for="aaseq">Enter the amino-acid sequence:</label>
<textarea name="aaseq" rows="5" cols="40"></textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-8">
<div class="form-group">
<label for="prop">Do you want to see the proportion of
charged amino-acid?</label>
<div class="radio">
<label>
<input type="radio" name="prop" value="y">Yes </label>
</div>
<div class="radio">
<label>
<input type="radio" name="prop" value="n">No
</label>
</div>
<label for="title">Job title (optional):</label>
<input type="text" size="30" name="title" value="">
<br>
<button type="submit" class="btn btn-primary">Send
</button>
</div>
</div>
</div>
</form>
</div>
</body>
</html>
```
La Figura 10.5 muestra cómo se representa el formulario del listado 10.4 en un navegador web.
{width=80%}

A continuación se muestra el código (`protcharge.py`) que se llamará cuando se utilice el formulario:
**Listado 10.5:** `protcharge.py`: Código back end para calcular la carga neta de una proteína y la proporción de aminoacidos cargados.
```
#!/usr/bin/env python
import cgi, cgitb
def chargeandprop(aa_seq):
protseq = aa_seq.upper()
charge = -0.002
cp=0
aa_charge = {'C':-.045,'D':-.999,'E':-.998,'H':.091,
'K':1,'R':1,'Y':-.001}
for aa in protseq:
charge += aa_charge.get(aa, 0)
if aa in aa_charge:
cp += 1
prop = float(cp)/len(aa_seq)*100
return (charge, prop)
cgitb.enable()
print('Content-Type: text/html\n')
form = cgi.FieldStorage()
seq = form.getvalue('aaseq', 'QWERTYYTREWQRTYEYTRQWE')
prop = form.getvalue('prop', 'n')
jobtitle = form.getvalue('title','No title')
charge, propvalue = chargeandprop(seq)
print('<html><body>Job title:{}<br/>'.format(jobtitle))
print('Your sequence is:<br/>{}<br/>'.format(seq))
print('Net charge: {}<br/>'.format(charge))
if prop == 'y':
print('Proportion of charged AA: {0:.2f}<br/>'
.format(propvalue))
print('</body></html>')
```
**Explicación del código**: el código para calcular la carga y la proporción de aminoácidos cargados se encuentra en la función que comienza en la línea 4. En la línea 19 creamos una instancia (*form*) de la clase `cgi.FieldStorage`. El objeto *form* es responsable de tomar los valores enviados por el formulario y hacer que estén disponibles en forma de diccionario. De la línea 20 a la 22 recuperamos los valores ingresados por el usuario. En la línea 24, se evalúa la "carga neta" y la "proporción de aminoácidos cargados". Desde la línea 25 hasta el final se genera el HTML que se enviará al navegador.
La figura 10.6 muestra la página HTML resultante después de que se ejecute el código del Listado 10.5.
## WSGI
Antes de WSGI había muchas opciones incompatibles para la programación web en Python. Algunos de ellos eran *frameworks*, es decir, un conjunto de programas para el desarrollo de sitios web dinámicos. El problema con algunos de estos frameworks fue que cada uno operaba de una manera diferente y la mayoría de ellos estaban vinculados a un servidor web, lo que limita la elección en el par servidor web/aplicaciones.

WSGI se creó para llenar este vacío y se define como una “interfaz simple y universal entre servidores web y aplicaciones web o frameworks”. Muchos componentes (o middleware) ahora son compatibles con WSGI, por lo que el programador no necesita tratar directamente con WSGI. Una vez que una aplicación funciona con un software intermedio se puede implementar en cualquier servidor compatible con WSGI. WSGI ahora está estandarizado y es parte del lenguaje de Python[^nota10-5]. Por estas razones WSGI es la opción recomendada para el desarrollo web en Python.
[^nota10-5]: Como se describe en PEP-3333 en <https://www.python.org/dev/peps/pep-3333/>.
### Bottle: un framework web de Python para WSGI
**Bottle** es un micro framework web para Python. Se distribuye como un solo archivo y no tiene dependencias, es decir, solo necesita Python para ejecutarse. **Bottle** proporciona 4 componentes básicos:
* Enrutamiento: una forma de traducir (o asignar) un URL a una función de Python. Entonces cada vez que un usuario solicita un URL (que puede tener partes variables) se ejecuta una función específica.
* Plantillas (templates): un motor de plantillas integrado y soporte incorporado para plantillas de terceros (mako, jinja2 y cheetah).
* Utilidades: un objeto similar a un diccionario para acceder a datos de formularios, cargas de archivos, cookies, encabezados y otros metadatos.
* Servidor: servidor de desarrollo y soporte para un servidor HTTP externo, incluido cualquier servidor HTTP compatible con WSGI.
Hay otras alternativas a **Bottle** y el más prominente es **Flask** (consultá la Tabla 10.1 al final de este capítulo para obtener más opciones)
### Instalación de Bottle
**Bottle** está disponible en su página web <https://bottlepy.org>, pero como la mayoría de los paquetes externos, se puede instalar con `pip install` en un entorno virtual. El siguiente fragmento de código muestra cómo crear el entorno virtual (denominado `bottleproj`), cómo activarlo y cómo instalar Bottle en el entorno virtual `bottleproj`:
{line-numbers=off,lang=text}
```
$ virtualenv bottleproj
Using base prefix '/usr'
New python executable in /home/sb/bottleproj/bin/python3
Also creating executable in /home/sb/bottleproj/bin/python
Installing setuptools, pip, wheel...done.
$ . bottleproj/bin/activate
(bottleproj) $ pip install bottle
Collecting bottle
Downloading bottle-0.12.13.tar.gz (70kB)
(...)
Successfully built bottle
Installing collected packages: bottle Successfully installed bottle-0.12.13
```
El comando equivalente para la distribución de Anaconda es:
{line-numbers=off,lang=text}
```
$ conda create -n bottleproj bottle
```
Dado que **Bottle** está contenido en un archivo, un método de instalación alternativo es descargar el archivo desde <https://raw.githubusercontent.com/bottlepy/bottle/master/bottle.py> y copiarlo en el mismo directorio donde está nuestro script.
### Aplicación mínima de Bottle
Aquí hay una aplicación simple "Hello World" en Bottle:
**Listado 10.6:** `hellobottle.py`: Hello world en Bottle.
```
from bottle import route, run
@route('/')
def index():
return '<h2>Hello World!</h2>' 6
run(host='localhost', port=8000)
```
En la primera línea importamos dos componentes de *Bottle* (`route` y `run`). En la línea 3 asignamos una ruta a una función que comienza en la siguiente línea. Este es el URL (dirección web) que el usuario debe ingresar después del dominio para obtener esta página. Cuando un usuario escribe esta ruta (en este caso, el nivel raíz) en su navegador, se ejecutará la función `index`. Esta función (en la línea 4) simplemente devuelve `<h2> Hello World! </h2>`. La línea 7 inicia el servidor.
Aquí está la salida de este programa en la terminal:
{line-numbers=off}
```
(bottleproj) $ python helloworldbottle.py
Bottle v0.13-dev server starting up (using WSGIRefServer())...
Listening on http://localhost:8000/
Hit Ctrl-C to quit.
```
{width=70%}

### Componentes de Bottle
**Routes**
Usando el decorador **@route** definimos cómo se verá el URL para cada página. El siguiente fragmento de código muestra 2 páginas, una página raíz sin ninguna ruta y una página acerca de con `/about` en el URL:
{line-numbers=off}
```
@route('/')
def index():
return 'Top level, or Index Page'
@route('/about')
def about():
return 'The about page'
```
**URL con partes variables**
En algunos sitios parte del URL son una o más variables que se pasan al servidor para crear la página web; por ejemplo, en <https://stackoverflow.com/questions/6224052>. La parte con el número `6224052` es una parte variable. Este número se pasa al programa y se usa como una clave para buscar el contenido del artículo en una base de datos.
El siguiente código muestra un URL con una parte fija y una parte variable. La parte fija es `/greets/` mientras que la parte variable se llama name. Cualquier cadena que esté en su lugar se pasará a la función asociada (`shows_greeting`) como parámetro.
{line-numbers=off}
```
@route('/greets/<name>')
def shows_greeting(name):
return 'Hello {0}'.format(name)
```
Si llegamos al URL <http://127.0.0.1:5000/greets/Adele>, vereremos una página con el texto `Hello Adele`.
**Obtención de datos: request**
**request** es un objeto similar a un diccionario con algunas propiedades útiles. Almacena cookies, valores enviados en un formulario, encabezados HTTP, archivos y más. Veamos algunas propiedades útiles:
* **request.form**: todas las variables de un formulario web están disponibles desde este objeto similar a un diccionario. Si hay datos en un campo del formulario llamado `username`, la forma de acceder al valor del campo es con `request.forms.get('username')`.
* **request.method**: el método HTTP utilizado al solicitar la página. Cuando un navegador trae una página web envía un tipo de solicitud 'GET'. Cuando se llama a un URL porque se está enviando un formulario, es una solicitud (request) "POST". Hay otro tipo de solicitudes (‘PUT', 'PATCH' y 'DELETE') pero no se cubrirán aquí.[^nota10-6]
[^nota10-6]: Para obtener más información sobre los métodos de solicitud, visite <http://www.w3schools.com/tags/ref_http://www.methods.asp>.
* **request.args**: para acceder a los parámetros enviados en el URL. Se usa cuando el URL tiene la forma `?key=value`, también es un objeto similar a un diccionario. Si tenemos un sitio con un URL como `http://example.com/position?lat=37.51&long=115.71`, hay dos claves, `lat` y `long`, cuyos valores son `37.51` y `115.71` respectivamente. Para recuperar `lat` podemos usar `request.args['lat']` o `request.args.get('lat')`. Se desaconsejan este tipo de URLs, los URL fáciles de usar son la norma, en este caso podría ser `http://example.com/ position/37.51/115.71`.[^nota10-7]
[^nota10-7]: Para obtener más consejos sobre los URL, consulte <https://support.google.com/webmasters/answer/76329>.
* **request.files**: cuando se carga un archivo se pasa al programa como `request.files['filename']`.
**Plantillas (templates)**
En ejemplos anteriores nuestros métodos devuelven cadenas (cadena de texto sin formato o cadena HTML). La forma preferida de crear un archivo HTML para enviar al usuario es tener una plantilla y datos variables, con ambos componentes hacer un HTML final (o renderizado). Para esto utilizamos el método **template** proporcionado por **Bottle**. La forma general del método **template** es: *template(template_name, ** dictionary)*. Una plantilla suele ser un archivo HTML con variables como señaladores para los valores finales. El siguiente texto es una plantilla con una variable:
**Listado 10.7:** `index.tpl`: Plantilla para Bottle con variables.
{line-numbers=off,lang=html}
```
<html lang="en">
<body>
<h1>Hello {{ name }}!</h1>
</body>
</html>
```
Si este archivo se llama `index.tpl` y se almacena en las carpeta `views`, puede ser renderizado con este código:
**Listado 10.8:** `indextemplate.py`: Código Bottle para plantilla con variables.
```
from bottle import route, run, template
@route('/greets/<username>')
def shows_greeting(username):
return template('index', **{'name':username})
run(host='localhost', port=8000)
```
Las plantillas también pueden tener comandos de control de flujo para que podamos controlar qué parte de la plantilla se representa. Como ejemplo veamos la siguiente plantilla (`index2.tpl`):
**Listado 10.9:** `index2.tpl`: Plantilla para Bottle con variables y control de flujo.
{line-numbers=on,lang=html}
```
<html lang="en">
<body>
%if name[0].isalpha():
<h1>Hello {{ name }}!</h1>
%else:
<h1>Your user name must can't start with a number</h1>
%end
</body>
</html>
```
Esta plantilla tiene un código parecido a Python en las líneas 3, 5 y 7. Se parece a Python, pero tiene un `%end` (línea 7) que no es parte de la sintaxis normal de Python. Esto se debe a que en Python los bloques de código están marcados con sangría y en las plantillas la sangría no se tiene en cuenta, por lo que la marca `%end` debe estar presente. Para usar esta plantilla cambie la línea 5 en el Listado 10.8 para que apunte al archivo `index2.tpl`. El nuevo listado (10.10) se llama `indextemplate2.py`:
**Listado 10.10:** `indextemplate2.py`: Código Bottle para plantilla con variables.
```
from bottle import route, run, template
@route('/greets/<username>')
def shows_greeting(username):
return template('index2', **{'name':username})
run(host='localhost', port=8000)
```
Este listado usa la plantilla `index2.tpl` (Listado 10.9) que, en la línea 3, verifica el primer carácter de la variable `name`. Si esto es verdad se imprimirá el mismo mensaje que en la primera plantilla, si no, imprimirá el mensaje que podemos ver en la línea 6 de la plantilla.
Tené en cuenta que podemos obtener el mismo resultado si tomamos la decisión (`if`) en el código, en lugar de hacerlo en la plantilla. Veamos el siguiente código y plantilla:
**Listado 10.11:** `indextemplate3.py`: Código Bottle con lógica en el código en lugar de plantillas.
```
from bottle import route, run, template
@route('/greets/<username>')
def shows_greeting(username):
if username[0].isalpha():
msg = 'Hello {0}!'.format(username)
else:
msg = "Your username must can't start with a number"
return template('index3', **{'msg':msg})
run(host='localhost', port=8000)
```
Plantilla para el Listado 10.11 (`index3.tpl`):
**Listado 10.12:** `index3.tpl`: plantilla para indextemplate3.py.
```
<html lang="en">
<body>
<h1>{{ msg }}</h1>
</body>
</html>
```
En el Listado 10.11 (archivo `indextemplate3.py`) verificamos la primera letra del nombre de usuario en la línea 6, por lo que nos apartamos de la lógica de la plantilla lo que resulta en una plantilla más fácil de leer. Dado que tanto el Listado 10.10 como el 10.11 producen el mismo resultado parece que ambas estrategias son equivalentes, pero no lo son. Las plantillas admiten la lógica, pero es mejor tener una lógica compleja en su código (donde tiene mejores herramientas para depurarlo) en lugar de en el HTML (donde generalmente un diseñador web, sin conocimientos en Python, lo va a poder editar). En este caso, el Listado 10.11 se prefiere sobre el 10.10. Esto no significa que debamos evitar el uso de la lógica en cualquier plantilla, a veces tiene mucho sentido, como en esta situación:
{line-numbers=off}
```
<ul>
% for item in items:
<li>{{item}}</li>
% end
</ul>
```
La conclusión de esto es utilizar la lógica en las plantillas donde estimemos que no hará que el sitio sea más difícil de mantener.
**Archivos estáticos**
Algunos archivos se sirven de forma estática, lo que significa que no se generan sobre la marcha por un proceso de back-end. Los casos más comunes son archivos *.css*, *.js* e imágenes. Se puede mostrar un archivo estático devolviendo una plantilla sin ninguna variable, pero **Bottle** tiene el método **static_file** para manejar estos archivos. **static_file** proporciona la funcionalidad adicional necesaria en este caso.[^nota10-8] Debe pasar el nombre del archivo y ruta donde reside este archivo:
[^nota10-8]: Ver <https://bottlepy.org/docs/dev/tutorial.html#tutorial-static-files> para obtener más información sobre este método.
{line-numbers=off}
```
@route('/static/rss.xml')
def rss_static():
return static_file('rss.xml', root='static/')
```
La ruta se puede pasar con partes variables encerrando la parte variable entre `<` y `>`:
{line-numbers=off}
```
@route('/static/js/<filename>')
def js_static(filename):
return static_file(filename, root='static/js/')
```
### Programa web para calcular la carga neta de una proteína (versión Bottle)

Aquí está la versión **Bottle** del programa web para calcular la carga neta de una proteína. Necesitamos una plantilla HTML para el formulario web. En este caso podemos usar el mismo archivo HTML que en `protchargeformcgi.html` con una modificación en la línea 9. El "action attribute" en el "form element" debe apuntar a un nuevo URL. Ahora se lee:
{line-numbers=off,lang=html}
```
<form action='/protcharge' method='post'>
```
El archivo completo se llama `protchargeformbottle.html` y se puede encontrar en el repositorio del libro en <https://github.com/ToyokoLabs/Py4Bio/tree/master/code>. Cuando se usa el formulario y el usuario presiona SEND, el navegador realizará una solicitud POST al URL `/protcharge`. Esto ejecutará el siguiente código:
**Listado 10.13:** `protchargebottle.py`: Back-end del programa para calcular la carga neta de una proteína usando Bottle.
```
from bottle import route, run, static_file, view, post, request
def chargeandprop(aa_seq):
""" Calculates protein net charge and charged AA proportion
"""
protseq = aa_seq.upper()
charge = -0.002
cp = 0
aa_charge = {'C':-.045,'D':-.999,'E':-.998,'H':.091,
'K': 1, 'R':1, 'Y':-0.001}
for aa in protseq:
charge += aa_charge.get(aa, 0)
if aa in aa_charge:
cp += 1
prop = float(cp)/len(aa_seq)*100
return (charge, prop)
@route('/')
def index():
return static_file('protchargeformbottle.html', root='views/')
@route('/css/<filename>')
def css_static(filename):
return static_file(filename, root='css/')
@post('/protcharge')
@view('result')
def protcharge():
seq = request.forms.get('aaseq', 'QWERTYYTREWQRTYEYTRQWE')
prop = request.forms.get('prop','n')
title = request.forms.get('title', 'No title')
charge, propvalue = chargeandprop(seq)
return {'seq': seq, 'prop': prop, 'title': title,
'charge': round(charge, 3), 'propvalue': propvalue}
run(host='localhost', port=8000)
```
En el Listado 10.13 (archivo `protchargebottle.py`) hay 4 funciones. Una función que maneja el cálculo de la carga neta real (`chargeandprop`) y tres que manejan el mapeo de diferentes URLs. La función `index` se ejecuta cuando el usuario accede a la página de inicio y devuelve el HTML con el formulario (el archivo `protchargeformbottle.html`), que se puede ver en la Figura 10.8. La función `css_static` devuelve el css necesario para la visualización correcta del formulario y la página de resultados. La función `protcharge` se ejecuta cuando el `domain/protcharge` del URL recibe un post request y se envía cuando el usuario presiona el botón "SEND" en el formulario del archivo `protchargeformbottle.html`. Esta función devuelve un diccionario con todos los valores necesarios para representar la página de resultados. La plantilla utilizada en este caso es el archivo `result.html` como se muestra en el decorador de vistas en la línea 27. Recuerde que este archivo debe estar en el directorio `view` para que este decorador funcione.
**Listado 10.14:** `result.html`: Plantilla para mostrar el resultado del método protcharge.
```
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8">
<title>Protein Charge Calculator: Result</title>
<link href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body style="background-color:#e7f5f5;">
<div class="container"><h2>Result</h2>
<p>Job title: {{title}}</p>
<p>Your sequence is: {{seq}}</p>
<p>Net charge: {{charge}}</p>
% if prop == 'y':
<p>Proportion of charged AA: {{propvalue}}<p> 14 % end
</div>
</body>
</html>
```
### Instalación de un programa WSGI en Apache
Hay varias formas de ejecutar un WSGI en el servidor web Apache. En este libro utilizaremos `mod_wsgi`, un módulo de Apache creado para alojar aplicaciones de Python que admite la interfaz WSGI de Python.
El módulo se puede descargar desde el sitio web del proyecto[^nota10-9] o puede ser instalado con el administrador de paquetes del sistema operativo.[^nota10-10]
Una vez que se haya instalado `mod_wsgi` debemos modificar el archivo apache.conf agregando una línea como esta:
[^nota10-9]: <http://code.google.com/p/modwsgi/>
[^nota10-10]: Se llama libapache2-mod-wsgi en sistemas basados en Debian.
{line-numbers=off}
```
WSGIScriptAlias webpath path_in_server
```
donde `webpath` es la ruta que ve el usuario y `path_in_server` es la ruta al archivo que recibirá todas las solicitudes en este directorio. Por ejemplo,
{line-numbers=off}
```
WSGIScriptAlias / /var/www/sitepath/htdocs/test.wsgi
```
Eso significa que cada solicitud que apunte a cualquier página en el directorio raíz del servidor web será manejada por un script ubicado en `/var/www/sitepath/htdocs/-test.wsgi`.
## OPCIONES ALTERNATIVAS PARA HACER SITIOS WEB DINÁMICOS BASADOS EN PYTHON
Las soluciones presentadas hasta este punto son lo suficientemente útiles para construir sitios pequeños y medianos desde cero. Pero si nuestro sitio web utiliza funciones avanzadas como soporte de bases de datos, administración de usuarios y sesiones, interfaz administrativa, internacionalización, almacenamiento en caché y otros, sería mejor usar un framework web de funciones completas donde la mayoría de estas funciones ya están cubiertas. Dado que estos tipos de aplicaciones están fuera del alcance de este libro, mostraré una tabla que resume los frameworks web más importantes en la Tabla 10.1. La tabla está ordenada aproximadamente según su nivel de abstracción. Las primeras entradas son sistemas con menos funciones y requieren más ajustes para lograr el mismo resultado que un framework de nivel superior.
Ningún framework ha recibido el estado de "framework web oficial de Python", por lo que hay algo de dispersión en el uso y desarrollo, pero **Django** es sin dudas el framework web más popular de Python. Si desea conocer el framework web más utilizado y compatible, **Django** es el camino a seguir.
## ALGUNAS PALABRAS SOBRE LA SEGURIDAD DE NUESTROS PROGRAMAS
Si tus programas se ejecutarán en entornos de confianza (es decir, no en Internet), podés omitir esta sección y pasar a la siguiente.
Algo que se debe tener en cuenta al diseñar aplicaciones web es que el usuario puede (y lo hará) ingresar datos en un formato inesperado. Cuando el formulario es accesible al público en Internet esta amenaza no debe ser subestimada.
Habrá personas que no sabrán cómo completar el formulario en línea y probar lo que crean que es mejor. Habrá atacantes que pondrán a prueba su sitio en busca de cualquier vulnerabilidad explotable.
Tabla 10.1 Frameworks para el desarrollo web
| Nombre | Descripción |
| ------ | ----------- |
| [Flask](https://palletsprojects.com/p/flask/) | Framework web simple y muy similar a Bottle. |
| [Tornado](https://www.tornadoweb.org/en/stable/) | Framework web y biblioteca de red asincrónica usada para requests largos y websockets. |
| [Plone](https://plone.org/) | Un sistema de gestión de contenido personalizable basado en Python. |
| [Django](https://www.djangoproject.com/) | Framework web de alto nivel de Python que fomenta el desarrollo rápido. |
| [TurboGears](https://turbogears.org/) | Solución completa y escalable con AJAX y compatibilidad con múltiples bases de datos. |
| [Web2py](http://www.web2py.com/) | Framework gratuito de código abierto y de pila completa para el desarrollo rápido de aplicaciones basadas en web rápidas, escalables, seguras y portátiles basadas en bases de datos. |
Una primera barrera que puede usarse para evitar el mal uso de sus programas es usar JavaScript (JS) para la validación de formularios. No es el propósito de este libro enseñar JS, por lo que hay enlaces en la sección "Recursos adicionales".
Se puede usar JS para evitar problemas relacionados con el usuario final, pero es bastante inútil como elemento disuasorio para cualquier persona que esté decidida a atacar su servidor. Si alguien quiere interactuar con tu página, puede hacerlo sin usar el navegador web, omitiendo completamente el código JS cuidadosamente creado. Esta es la razón por la que toda la validación de los datos también debe hacerse en el "lado del servidor".
Otro punto crítico a tener en cuenta es cuando un programa accede a un motor de base de datos existe la posibilidad de que un atacante inyecte comandos SQL para producir resultados no deseados (como enumerar el contenido completo de una tabla con información confidencial como nombres de usuario y contraseñas[^nota10-11]). Este tipo de ataque se llama "inyección SQL" y se tratará en el capítulo "Python y bases de datos" (Capítulo 12).
No existe una regla general sobre cómo sanear todo tipo de información, depende de la aplicación particular. A continuación hay algunos esquemas a tener en cuenta en el momento de diseñar la seguridad de su aplicación.
[^nota10-11]: No debe almacenar contraseñas en texto sin formato en una base de datos. La mejor práctica es almacenar un hash de la contraseña, utilizando una función hash como PBKDF2 o bcrypt. Además del hash, también debemos agregar un poco de "sal", es decir, una cadena aleatoria para evitar que un atacante use las teclas de hash preestablecidas para encontrar una coincidencia. No debemos utilizar algoritmos personalizados no probados o funciones hash criptográficas rápidas como MD5, SHA1, SHA512, etc.
1. Identificar dónde pueden acceder a los datos de la aplicación. Claramente, el punto de entrada más evidente son los formularios configurados para la entrada de datos. Pero no debe pasar por alto otros puntos de entrada como el URL, archivos almacenados en el servidor y otros sitios web, si nuestros programas leen fuentes externas como feeds RSS.
2. Estar atentos a los caracteres de escape usado por el programa con los que interactúa nuestra aplicación. Estos siempre deben ser filtrados. Si nuestro programa accede a un shell Unix, filtre el carácter ";" (punto y coma) ya que puede usarse para emitir comandos arbitrarios. Esto depende del tipo de shell que esté usando su sistema. Algunos de los caracteres que debemos considerar son:;, &&, ||, \ y ".
3. Considerar hacer una lista de los caracteres válidos y aceptados (una "lista de permitidos") para asegurarse de que sus cadenas tengan solo los caracteres requeridos.
4. Los privilegios de ejecución del programa del servidor web deben ser los más bajos posibles. La mayoría de los sistemas Unix utilizan un usuario ad hoc para el proceso del servidor web. Esto se denomina “Principio de privilegio mínimo”. El programa recibe la cantidad mínima de privilegios necesarios para hacer su trabajo. Esto limita el abuso que se puede hacer a un sistema si el proceso del servidor web es secuestrado por un atacante.
## DONDE ALOJAR LOS PROGRAMAS DE PYTHON
Si hemos probado satisfactoriamente nuestros programas en su servidor local, es hora de ponerlos en Internet para que el resto del mundo los pueda disfrutar. Por lo general, las instituciones para la que trabajamos tienen un servidor web donde podemos almacenar nuestros programas, para lo cual el primer paso sería solicitar asistencia a su departamento de IT. En el caso de que no obtenga una respuesta satisfactoria deberíamos considerar resolver el problema nosotros mismos. No es demasiado difícil, hay miles de empresas de alojamiento web. Busquen uno que apoye explícitamente a Python.
Entre los diversos planes que ofrecen las empresas de alojamiento web, elegí el tipo de plan "compartido" si tu programa es muy simple y no implica la instalación de programas o módulos adicionales. Si su programa ejecuta programas que no están instalados en el servidor, como es el caso de Biopython, puede solicitar que se instale. Preguntá antes de contratar el servicio si instalan módulos a pedido. Otro problema que puede surgir es con los frameworks web, algunos funcionan como un proceso de ejecución prolongada que no está permitido por el acuerdo de alojamiento.
Asegurate de que la versión de Python instalada en el servidor de alojamiento sea compatible con tus programas. Este no es un tema menor considerando que los sistemas operativos utilizados para servidores tienden a usar una versión "estable" de todos los programas en lugar de la última.
Si el servicio de alojamiento web no permite la instalación del programa, deberá considerar una solución de alojamiento dedicada, donde tenga acceso de root a una computadora, donde no hay límites con respecto a lo que podemos instalar. Gracias a las tecnologías de virtualización, es posible contratar un plan de alojamiento virtual dedicado a un precio más que accesible (también conocido como Servidor Virtual Privado de VPS). Esto se debe a que la computadora se comparte entre varios usuarios, pero difiere del tipo de plan de hospedaje compartido en que cada usuario tiene acceso total al servidor. Para aplicaciones muy exigentes, esta puede no ser la mejor solución y es posible que tengamos que recurrir al uso de un alojamiento dedicado (no virtual).
Una alternativa a los servidores es el motor de aplicaciones de Google. Este sistema le permite crear aplicaciones web en los mismos sistemas escalables que impulsan las aplicaciones de Google. Dejá que Google se encargue de los archivos de configuración del servidor web Apache, los scripts de inicio, las bases de datos, el monitoreo del servidor y las actualizaciones de software. Solo escribimos nuestro código de Python. Las aplicaciones diseñadas para este motor se implementan utilizando el lenguaje de programación Python. El entorno de ejecución de App Engine Python incluye una versión especializada del intérprete de Python. Para obtener más información sobre "Google App Engine", consultá <https://cloud.google.com>.
Amazon Web Services (AWS) también tiene una solución "sin servidor" llamada Lambda. Con AWS Lambda, nos enfocamos solo en el código y Lambda administra la flota de cómputo que ofrece un balance de memoria, CPU, red y otros recursos. La desventaja es que se paga por la ejecución del programa y no se pueden ejecutar los programas como están escritos; se necesita hacer algunos ajustes para que sea compatible con este entorno en particular. Funciona solo con algunos lenguajes de programación, pero Python está incluido. Para obtener más información, consultá <https://aws.amazon.com/lambda/> y <http://docs.aws.amazon.com/lambda>.
## RECURSOS ADICIONALES
* [W3Schools](https://www.w3schools.com/js/js_validation.asp): JavaScript form validation.
* [Data validation](https://www.owasp.org/index.php/Data_Validation)
* [JavaScript-Coder.com](http://www.javascript-coder.com/html-form/javascript-form-validation.phtml): JavaScript form validation : Quick and Easy!
* [HTML reference](http://htmlreference.io): A free guide to HTML.
* [Bootstrap](http://getbootstrap.com): The most popular HTML, CSS, and JS framework for developing responsive, mobile first projects on the web.
* [PureCSS](http://purecss.io): A set of small, responsive CSS modules that you can use in every web project.
* [BottlePlate](https://github.com/Rolinh/bottleplate): A bottle template for python 3.3+ web applications or API servers.
* [Bottle + uWSGI](https://goo.gl/X8Up6S): simple web app configuration and fun hidden features.
* [Django vs Flask](https://www.git-pull.com/code_explorer/django-vs-flask.html)
* [Decanter](http://gengo.github.io/decanter/): Creates a Bottle based directory structure with an example view and controller.
* [Learn web development](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django)
* [Web programming in Python](http://wiki.python.org/moin/WebProgramming)
* [wuzz](https://github.com/asciimoo/wuzz): An interactive command line tool for HTTP inspection.
X> ## AUTOEVALUACIÓN
X>
X> 1- ¿Qué es CGI?
X>
X> 2- ¿Qué es WSGI? ¿Por qué es la opción recomendada para la programación web?
X>
X> 3- ¿Cuál es la razón para usar Bottle o cualquier otro "web framework"?
X>
X> 4- ¿Qué es un lenguaje de plantilla?
X>
X> 5- ¿Qué es un archivo estático y por qué debería servirlo de una manera diferente?
X>
X> 6- Python incluye un servidor web limitado. ¿Por qué utilizaría un servidor web de este tipo si hay servidores web completos y gratuitos como Apache?
X>
X> 7- Nombrar las consideraciones de seguridad que se deben tener en cuenta al ejecutar un servidor web en Internet.
X>
X> 8- ¿Por qué la validación de datos del lado del cliente no es útil como validación de datos del lado del servidor?
X>
X> 9- ¿Cuál es la diferencia entre un hosting compartido, dedicado, virtual dedicado? ¿Cuándo deberíamos utilizar un hosting dedicado en lugar de un plan compartido?