http.server — Clases base para implementar servidores Web¶
Propósito: | http.server incluye clases que pueden formar la base de un servidor Web. |
---|
http.server
usa clases de socketserver
para crear clases base para
hacer servidores HTTP. HTTPServer
puede ser usada directamente, pero la
BaseHTTPRequestHandler
está destinada a ser ampliada para manejar cada
método de del protocolo (GET, POST, etc.).
HTTP GET¶
Para agregar soporte para un método HTTP en una clase de manejador de
solicitudes, implementa el método do_METHOD()
, reemplazando METHOD
con
el nombre del método HTTP (por ejemplo, do_GET()
, do_POST()
, etc.).
Por consistencia, los métodos de manejo de solicitudes no toman argumentos.
Todos los parámetros para la solicitud son analizados por
BaseHTTPRequestHandler
y almacenados como atributos de la instancia de
solicitud.
Este controlador de solicitud de ejemplo ilustra cómo devolver una respuesta al cliente, y algunos de los atributos locales que pueden ser útiles para construir la respuesta.
from http.server import BaseHTTPRequestHandler
from urllib import parse
class GetHandler(BaseHTTPRequestHandler):
def do_GET(self):
parsed_path = parse.urlparse(self.path)
message_parts = [
'CLIENT VALUES:',
'client_address={} ({})'.format(
self.client_address,
self.address_string()),
'command={}'.format(self.command),
'path={}'.format(self.path),
'real path={}'.format(parsed_path.path),
'query={}'.format(parsed_path.query),
'request_version={}'.format(self.request_version),
'',
'SERVER VALUES:',
'server_version={}'.format(self.server_version),
'sys_version={}'.format(self.sys_version),
'protocol_version={}'.format(self.protocol_version),
'',
'HEADERS RECEIVED:',
]
for name, value in sorted(self.headers.items()):
message_parts.append(
'{}={}'.format(name, value.rstrip())
)
message_parts.append('')
message = '\r\n'.join(message_parts)
self.send_response(200)
self.send_header('Content-Type',
'text/plain; charset=utf-8')
self.end_headers()
self.wfile.write(message.encode('utf-8'))
if __name__ == '__main__':
from http.server import HTTPServer
server = HTTPServer(('localhost', 8080), GetHandler)
print('Starting server, use <Ctrl-C> to stop')
server.serve_forever()
El texto del mensaje se ensambla y luego se escribe en wfile
, el
manejador de archivo que envuelve el socket de respuesta. Cada respuesta
necesita un código de respuesta, establecido a través de send_response()
.
Si se utiliza un código de error (404, 501, etc.), se incluye un mensaje de
error predeterminado apropiado en el encabezado, o se puede pasar un mensaje
con el código de error.
Para ejecutar el manejador de solicitudes en un servidor, pásalo al constructor
de HTTPServer
, en la parte de procesamiento __main__
de la secuencia de
comandos de ejemplo.
A continuación, inicia el servidor:
$ python3 http_server_GET.py
Starting server, use <Ctrl-C> to stop
En una terminal separada, usa curl
para acceder a él:
$ curl -v -i http://127.0.0.1:8080/?foo=bar
* Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET /?foo=bar HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
HTTP/1.0 200 OK
Content-Type: text/plain; charset=utf-8
Server: BaseHTTP/0.6 Python/3.5.2
Date: Thu, 06 Oct 2016 20:44:11 GMT
CLIENT VALUES:
client_address=('127.0.0.1', 52934) (127.0.0.1)
command=GET
path=/?foo=bar
real path=/
query=foo=bar
request_version=HTTP/1.1
SERVER VALUES:
server_version=BaseHTTP/0.6
sys_version=Python/3.5.2
protocol_version=HTTP/1.0
HEADERS RECEIVED:
Accept=*/*
Host=127.0.0.1:8080
User-Agent=curl/7.43.0
* Connection #0 to host 127.0.0.1 left intact
Nota
La salida producida por diferentes versiones de curl
puede variar. Si la
ejecución de los ejemplos produce una salida diferente, verifica el número de
versión reportado por curl
.
HTTP POST¶
Soportar solicitudes POST es un poco más de trabajo, porque la clase base no
analiza los datos del formulario automáticamente. El módulo cgi
proporciona la clase FieldStorage
que sabe cómo analizar el formulario, si
se le da las entradas correctas.
import cgi
from http.server import BaseHTTPRequestHandler
import io
class PostHandler(BaseHTTPRequestHandler):
def do_POST(self):
# Parse the form data posted
form = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={
'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': self.headers['Content-Type'],
}
)
# Begin the response
self.send_response(200)
self.send_header('Content-Type',
'text/plain; charset=utf-8')
self.end_headers()
out = io.TextIOWrapper(
self.wfile,
encoding='utf-8',
line_buffering=False,
write_through=True,
)
out.write('Client: {}\n'.format(self.client_address))
out.write('User-agent: {}\n'.format(
self.headers['user-agent']))
out.write('Path: {}\n'.format(self.path))
out.write('Form data:\n')
# Echo back information about what was posted in the form
for field in form.keys():
field_item = form[field]
if field_item.filename:
# The field contains an uploaded file
file_data = field_item.file.read()
file_len = len(file_data)
del file_data
out.write(
'\tUploaded {} as {!r} ({} bytes)\n'.format(
field, field_item.filename, file_len)
)
else:
# Regular form value
out.write('\t{}={}\n'.format(
field, form[field].value))
# Disconnect our encoding wrapper from the underlying
# buffer so that deleting the wrapper doesn't close
# the socket, which is still being used by the server.
out.detach()
if __name__ == '__main__':
from http.server import HTTPServer
server = HTTPServer(('localhost', 8080), PostHandler)
print('Starting server, use <Ctrl-C> to stop')
server.serve_forever()
Ejecuta el servidor en una ventana:
$ python3 http_server_POST.py
Starting server, use <Ctrl-C> to stop
Los argumentos para curl
pueden incluir datos de formulario para ser
publicados en el servidor usando la opción -F
. El último argumento, -F
datafile=@http_server_GET.py
, publica el contenido del archivo
http_server_GET.py
para ilustrar la lectura de datos de archivos del
formulario.
$ curl -v http://127.0.0.1:8080/ -F name=dhellmann -F foo=bar \
-F datafile=@http_server_GET.py
* Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> POST / HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Length: 1974
> Expect: 100-continue
> Content-Type: multipart/form-data;
boundary=------------------------a2b3c7485cf8def2
>
* Done waiting for 100-continue
HTTP/1.0 200 OK
Content-Type: text/plain; charset=utf-8
Server: BaseHTTP/0.6 Python/3.5.2
Date: Thu, 06 Oct 2016 20:53:48 GMT
Client: ('127.0.0.1', 53121)
User-agent: curl/7.43.0
Path: /
Form data:
name=dhellmann
Uploaded datafile as 'http_server_GET.py' (1612 bytes)
foo=bar
* Connection #0 to host 127.0.0.1 left intact
Hilos y bifuración¶
HTTPServer
es una subclase simple de socketserver.TCPServer
, y no usa
múltiples hilos o procesos para manejar las solicitudes. Para agregar hilos o
bifurcación, crea una nueva clase utilizando la combinación apropiada de
socketserver
.
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
import threading
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-Type',
'text/plain; charset=utf-8')
self.end_headers()
message = threading.currentThread().getName()
self.wfile.write(message.encode('utf-8'))
self.wfile.write(b'\n')
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
"""Handle requests in a separate thread."""
if __name__ == '__main__':
server = ThreadedHTTPServer(('localhost', 8080), Handler)
print('Starting server, use <Ctrl-C> to stop')
server.serve_forever()
Ejecute el servidor de la misma manera que los otros ejemplos.
$ python3 http_server_threads.py
Starting server, use <Ctrl-C> to stop
Cada vez que el servidor recibe una solicitud, inicia un nuevo hilo o proceso para manejarla:
$ curl http://127.0.0.1:8080/
Thread-1
$ curl http://127.0.0.1:8080/
Thread-2
$ curl http://127.0.0.1:8080/
Thread-3
El intercambio de ForkingMixIn
por ThreadingMixIn
logrará resultados
similares, utilizando procesos separados en lugar de hilos.
Manejo de errores¶
Maneja los errores llamando a send_error()
, pasando el código de error
apropiado y un mensaje de error opcional. La respuesta completa (con
encabezados, código de estado y cuerpo) se genera automáticamente.
from http.server import BaseHTTPRequestHandler
class ErrorHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_error(404)
if __name__ == '__main__':
from http.server import HTTPServer
server = HTTPServer(('localhost', 8080), ErrorHandler)
print('Starting server, use <Ctrl-C> to stop')
server.serve_forever()
En este caso, siempre se devuelve un error 404.
$ python3 http_server_errors.py
Starting server, use <Ctrl-C> to stop
El mensaje de error se informa al cliente utilizando un documento HTML como así como el encabezado para indicar un código de error.
$ curl -i http://127.0.0.1:8080/
HTTP/1.0 404 Not Found
Server: BaseHTTP/0.6 Python/3.5.2
Date: Thu, 06 Oct 2016 20:58:08 GMT
Connection: close
Content-Type: text/html;charset=utf-8
Content-Length: 447
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html;charset=utf-8">
<title>Error response</title>
</head>
<body>
<h1>Error response</h1>
<p>Error code: 404</p>
<p>Message: Not Found.</p>
<p>Error code explanation: 404 - Nothing matches the
given URI.</p>
</body>
</html>
Configuración de encabezados¶
El método send_header
agrega datos del encabezado a la respuesta HTTP.
Toma dos argumentos: el nombre del encabezado y el valor.
from http.server import BaseHTTPRequestHandler
import time
class GetHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header(
'Content-Type',
'text/plain; charset=utf-8',
)
self.send_header(
'Last-Modified',
self.date_time_string(time.time())
)
self.end_headers()
self.wfile.write('Response body\n'.encode('utf-8'))
if __name__ == '__main__':
from http.server import HTTPServer
server = HTTPServer(('localhost', 8080), GetHandler)
print('Starting server, use <Ctrl-C> to stop')
server.serve_forever()
Este ejemplo establece el encabezado Last-Modified
en marca actual de
tiempo, formateada de acuerdo con RFC 7231.
$ curl -i http://127.0.0.1:8080/
HTTP/1.0 200 OK
Server: BaseHTTP/0.6 Python/3.5.2
Date: Thu, 06 Oct 2016 21:00:54 GMT
Content-Type: text/plain; charset=utf-8
Last-Modified: Thu, 06 Oct 2016 21:00:54 GMT
Response body
El servidor registra la solicitud a la terminal, como en los otros ejemplos.
$ python3 http_server_send_header.py
Starting server, use <Ctrl-C> to stop
127.0.0.1 - - [06/Oct/2016 17:00:54] "GET / HTTP/1.1" 200 -
Uso de la línea de comando¶
http.server
incluye un servidor incorporado para servir archivos desde el
sistema de archivos local. Inícialo desde la línea de comando usando la opción
-m
de el intérprete de Python.
$ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 ...
127.0.0.1 - - [06/Oct/2016 17:12:48] "HEAD /index.rst HTTP/1.1" 200 -
El directorio raíz del servidor es el directorio de trabajo donde el se inicia el servidor.
$ curl -I http://127.0.0.1:8080/index.rst
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.5.2
Date: Thu, 06 Oct 2016 21:12:48 GMT
Content-type: application/octet-stream
Content-Length: 8285
Last-Modified: Thu, 06 Oct 2016 21:12:10 GMT
Ver también
- Documentación de la biblioteca estándar para http.server
socketserver
– El módulosocketserver
proporciona la clase base que maneja la conexión del conector en bruto.- RFC 7231 – «Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content» Incluye una especificación para el formato de encabezado y fechas HTTP.