2012-04-11 16 views
5

Tengo un servidor web muy simple escrito en Python. Escucha en el puerto 13000, ¿cómo puedo hacer que entregue una página web sencilla de "Hola mundo" si se abre http://localhost:13000 en el navegador?¿Cómo enviar correctamente la respuesta HTTP con Python usando solo la biblioteca de socket?

Justo ahí es mi código:

# set up socket and connection 
while True: 
    sock, addr = servSock.accept() 
    # WHAT GOES HERE? 
    sock.close() 

Como se puede ver, no estoy muy seguro de cómo enviar realmente volver la página web?

Solo tengo que utilizar la biblioteca socket.

EDIT: El problema no es que no sé cómo formular la respuesta HTTP, no sé cómo realmente hacer que se muestre en mi navegador! Simplemente sigue girando/cargando.

Respuesta

9

Actualización de acuerdo con el cambio pregunta

Posiblemente, sigue girando debido a que en combinación de ausencia de Content-Length y Connection cabeceras, navegador puede asumir que es Connection: keep-alive, por lo que continúa recibiendo los datos de su servidor para siempre. Intente enviar Connection: close, y pase Content-Length real para ver si eso ayuda.


¿No hará esto lo que usted espera? :)

#!/usr/bin/env python 
# coding: utf8 

import socket 

MAX_PACKET = 32768 

def recv_all(sock): 
    r'''Receive everything from `sock`, until timeout occurs, meaning sender 
    is exhausted, return result as string.''' 

    # dirty hack to simplify this stuff - you should really use zero timeout, 
    # deal with async socket and implement finite automata to handle incoming data 

    prev_timeout = sock.gettimeout() 
    try: 
     sock.settimeout(0.01) 

     rdata = [] 
     while True: 
      try: 
       rdata.append(sock.recv(MAX_PACKET)) 
      except socket.timeout: 
       return ''.join(rdata) 

     # unreachable 
    finally: 
     sock.settimeout(prev_timeout) 

def normalize_line_endings(s): 
    r'''Convert string containing various line endings like \n, \r or \r\n, 
    to uniform \n.''' 

    return ''.join((line + '\n') for line in s.splitlines()) 

def run(): 
    r'''Main loop''' 

    # Create TCP socket listening on 10000 port for all connections, 
    # with connection queue of length 1 
    server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, \ 
           socket.IPPROTO_TCP) 
    server_sock.bind(('0.0.0.0', 13000)) 
    server_sock.listen(1) 

    while True: 
     # accept connection 
     client_sock, client_addr = server_sock.accept() 

     # headers and body are divided with \n\n (or \r\n\r\n - that's why we 
     # normalize endings). In real application usage, you should handle 
     # all variations of line endings not to screw request body 
     request = normalize_line_endings(recv_all(client_sock)) # hack again 
     request_head, request_body = request.split('\n\n', 1) 

     # first line is request headline, and others are headers 
     request_head = request_head.splitlines() 
     request_headline = request_head[0] 
     # headers have their name up to first ': '. In real world uses, they 
     # could duplicate, and dict drops duplicates by default, so 
     # be aware of this. 
     request_headers = dict(x.split(': ', 1) for x in request_head[1:]) 

     # headline has form of "POST /can/i/haz/requests HTTP/1.0" 
     request_method, request_uri, request_proto = request_headline.split(' ', 3) 

     response_body = [ 
      '<html><body><h1>Hello, world!</h1>', 
      '<p>This page is in location %(request_uri)r, was requested ' % locals(), 
      'using %(request_method)r, and with %(request_proto)r.</p>' % locals(), 
      '<p>Request body is %(request_body)r</p>' % locals(), 
      '<p>Actual set of headers received:</p>', 
      '<ul>', 
     ] 

     for request_header_name, request_header_value in request_headers.iteritems(): 
      response_body.append('<li><b>%r</b> == %r</li>' % (request_header_name, \ 
                request_header_value)) 

     response_body.append('</ul></body></html>') 

     response_body_raw = ''.join(response_body) 

     # Clearly state that connection will be closed after this response, 
     # and specify length of response body 
     response_headers = { 
      'Content-Type': 'text/html; encoding=utf8', 
      'Content-Length': len(response_body_raw), 
      'Connection': 'close', 
     } 

     response_headers_raw = ''.join('%s: %s\n' % (k, v) for k, v in \ 
               response_headers.iteritems()) 

     # Reply as HTTP/1.1 server, saying "HTTP OK" (code 200). 
     response_proto = 'HTTP/1.1' 
     response_status = '200' 
     response_status_text = 'OK' # this can be random 

     # sending all this stuff 
     client_sock.send('%s %s %s' % (response_proto, response_status, \ 
                 response_status_text)) 
     client_sock.send(response_headers_raw) 
     client_sock.send('\n') # to separate headers from body 
     client_sock.send(response_body_raw) 

     # and closing connection, as we stated before 
     client_sock.close() 

run() 

Para una descripción más detallada, véase description of HTTP protocol.

+1

Sí, pero ... ¿el navegador simplemente "gira" y no se muestra nada? – antonpug

+0

He actualizado el ejemplo de código para trabajar 100% garantizado :) Espero que encuentre útiles los principios básicos del manejo de encabezados, pero le recomiendo que no confíe en este tipo de código e implemente el analizador de solicitudes HTTP con todas las funciones. – toriningen

+1

Bueno, has hecho una respuesta bastante completa ... aunque creo que la razón por la que todavía estaba girando (hasta el tiempo de espera) es que estaba esperando un doble "\ n". Pero al menos, su ejemplo de código es un buen fragmento para tener debajo del gancho, por las dudas;) – zmo

4

enviar de vuelta algo como:

HTTP/1.1 200 OK 
Date: Wed, 11 Apr 2012 21:29:04 GMT 
Server: Python/6.6.6 (custom) 
Content-Type: text/html 

continuación, el código HTML real. Asegúrese de que haya una línea nueva después de la línea de tipo de contenido y antes del html.

1

o, si lo que no quiere recordar el protocolo completo, se puede encontrar de nuevo usando:

% nc stackoverflow.com 80 
GET/HTTP/1.1 
Host: stackoverflow.com 

HTTP/1.1 200 OK 
Cache-Control: public, max-age=60 
Content-Type: text/html; charset=utf-8 
Expires: Wed, 11 Apr 2012 21:33:49 GMT 
Last-Modified: Wed, 11 Apr 2012 21:32:49 GMT 
Vary: * 
Date: Wed, 11 Apr 2012 21:32:49 GMT 
Content-Length: 206008 

[...] 
% 

así, deberá por lo general prefieren un sitio que es menos detallado (por lo general sólo sirve una archivo estático) que stackoverflow;)

los requisitos mínimos (que encontrará en la respuesta) es:

sock.send(r'''HTTP/1.0 200 OK 
Content-Type: text/plain 

Hello, world! 

''') 

dos retornos son obligatorios para el servidor para obtener la respuesta, ot herwise espera el navegador de forma indefinida para los encabezados

Pero para imitar el comportamiento de un servidor web, no se olvide de enviar su respuesta solamente después de que el navegador le envía algunos datos seguidos por dos retornos de carro, por lo general usted puede conseguir lo que se envía usando:

% nc -kl localhost 13000 
GET/HTTP/1.1 
Host: localhost:13000 
User-Agent: Mozilla/5.0... 
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 
Accept-Language: en-us,en;q=0.5 
Accept-Encoding: gzip, deflate 
DNT: 1 
Connection: keep-alive 

% 

para que pueda mejorar sus rutinas de prueba

1

es posible que desee a la comprobación objetos web http://www.webob.org/

es un sim proyecto liviano para crear solicitudes y respuestas compatibles con http. Usted puede hacer casi cualquier cosa con que las peticiones/objetos de respuesta ... O simplemente delegar el trabajo pesado a WebObjects

muestra

>>> from webob import Response 
>>> res = Response() 
>>> res.status 
'200 OK' 
>>> res.headerlist 
[('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')] 
>>> res.body 
'' 
2
# set up socket and connection 
while True: 
sock, addr = servSock.accept() 
sock.send("HTTP/1.1 200 OK\n" 
     +"Content-Type: text/html\n" 
     +"\n" # Important! 
     +"<html><body>Hello World</body></html>\n"); 
sock.close() 
0

Tomé una respuesta anterior y editado el código para python3 utf -8 y bytes de codificación. Gracias por la respuesta original que ayudó mucho.

import socket 

MAX_PACKET = 32768 

def recv_all(sock): 
    r'''Receive everything from `sock`, until timeout occurs, meaning sender 
    is exhausted, return result as string.''' 

    # dirty hack to simplify this stuff - you should really use zero timeout, 
    # deal with async socket and implement finite automata to handle incoming data 

    prev_timeout = sock.gettimeout() 
    try: 
     sock.settimeout(0.1) 

     rdata = [] 
     while True: 
      try: 
       # Gotta watch for the bytes and utf-8 encoding in Py3 
       rdata.append(sock.recv(MAX_PACKET).decode('utf-8')) 
      except socket.timeout: 
       return ''.join(rdata) 

     # unreachable 
    finally: 
     sock.settimeout(prev_timeout) 

def normalize_line_endings(s): 
    r'''Convert string containing various line endings like \n, \r or \r\n, 
    to uniform \n.''' 
    test = s.splitlines() 
    return ''.join((line + '\n') for line in s.splitlines()) 

def run(): 
    r'''Main loop''' 

    # Create TCP socket listening on 10000 port for all connections, 
    # with connection queue of length 1 
    server_sock = socket.socket(socket.AF_INET, 
           socket.SOCK_STREAM, 
           socket.IPPROTO_TCP) 
    #Added the port 13001 for debuging purposes 

    try: 
     server_sock.bind(('0.0.0.0', 13000)) 
     print('PORT 13000') 
    except: 
     server_sock.bind(('0.0.0.0', 13001)) 
     print('PORT 13001') 
    # except: 
    #  server_sock.bind(('0.0.0.0', 13002)) 
    #  print('PORT 13002') 

    server_sock.listen(1) 

    while True: 
     # accept connection 
     try: 
      client_sock, client_addr = server_sock.accept() 

      # headers and body are divided with \n\n (or \r\n\r\n - that's why we 
      # normalize endings). In real application usage, you should handle 
      # all variations of line endings not to screw request body 
      request = normalize_line_endings(recv_all(client_sock)) # hack again 

      request_head, request_body = request.split('\n\n', 1) 

      # first line is request headline, and others are headers 
      request_head = request_head.splitlines() 
      request_headline = request_head[0] 
      # headers have their name up to first ': '. In real world uses, they 
      # could duplicate, and dict drops duplicates by default, so 
      # be aware of this. 
      request_headers = dict(x.split(': ', 1) for x in request_head[1:]) 

      # headline has form of "POST /can/i/haz/requests HTTP/1.0" 
      request_method, request_uri, request_proto = request_headline.split(' ', 3) 

      response_body = [ 
       '<html><body><h1 style="color:red">Hello, world!</h1>', 
       '<p>This page is in location %(request_uri)r, was requested ' % locals(), 
       'using %(request_method)r, and with %(request_proto)r.</p>' % locals(), 
       '<p>Request body is %(request_body)r</p>' % locals(), 
       '<p>Actual set of headers received:</p>', 
       '<ul>', 
      ] 

      for request_header_name, request_header_value in request_headers.items(): 
       response_body.append('<li><b>%r</b> == %r</li>' % (request_header_name, 
                    request_header_value)) 

      response_body.append('</ul></body></html>') 

      response_body_raw = ''.join(response_body) 

      # Clearly state that connection will be closed after this response, 
      # and specify length of response body 
      response_headers = { 
       'Content-Type': 'text/html; encoding=utf8', 
       'Content-Length': len(response_body_raw), 
       'Connection': 'close', 
      } 

      response_headers_raw = ''.join('%s: %s\n' % (k, v) for k, v in \ 
                response_headers.items()) 

      # Reply as HTTP/1.1 server, saying "HTTP OK" (code 200). 
      response_proto = 'HTTP/1.1'.encode() 
      response_status = '200'.encode() 
      response_status_text = 'OK'.encode() # this can be random 

      # sending all this stuff 
      client_sock.send(b'%s %s %s' % (response_proto, response_status, 
                  response_status_text)) 
      client_sock.send(response_headers_raw.encode()) 
      client_sock.send(b'\n') # to separate headers from body 
      client_sock.send(response_body_raw.encode()) 

      # and closing connection, as we stated before 

     finally: 
      client_sock.close() 

run() 
Cuestiones relacionadas