2012-04-11 14 views
5

Ho un web sever molto semplice scritto in Python. Ascolta sulla porta 13000, come posso consegnare una semplice pagina Web "Hello World" se http://localhost:13000 è aperto nel browser?Come inviare correttamente la risposta HTTP con Python usando solo la libreria socket?

Proprio lì è il mio codice:

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

Come potete vedere, io non sono esattamente sicuro come inviare in realtà di nuovo la pagina web?

Devo solo usare la libreria socket.

EDIT: Il problema non è che non so come formulare la risposta HTTP, non so come renderlo effettivamente visualizzato nel mio browser! Continua a girare/caricare.

risposta

9

Aggiornato secondo cambiamento domanda

Possibilmente, si continua a girare, perché in combinazione di absense di Content-Length e Connection intestazioni, browser potrebbe assumere è Connection: keep-alive, così continua a ricevere i dati dal server per sempre. Prova a inviare Connection: close e passa il Content-Length effettivo per vedere se questo aiuta.


Questo non farà ciò che ti aspetti? :)

#!/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() 

Per una descrizione più dettagliata, vedere description of HTTP protocol.

+1

Sì ma ... il browser continua a "girare" e non viene visualizzato nulla? – antonpug

+0

Ho aggiornato un esempio di codice per funzionare garantito al 100% :) Spero che troverai utili i principi di base della gestione dell'intestazione, ma ti consiglio di non fare affidamento su questo tipo di codice e di implementare un parser di richieste HTTP completo. – toriningen

+1

beh, hai fatto una risposta abbastanza completa ... anche se penso che il motivo per cui stava ancora girando (fino al timeout) è che stava aspettando un doppio "\ n". Ma almeno, il tuo esempio di codice è un buon frammento da tenere sotto il gancio, per ogni evenienza;) – zmo

4

Invia indietro qualcosa come:

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

Poi il codice html vero e proprio. Assicurati che ci sia una newline dopo la riga Content-Type e prima dell'Html.

1

o, se proprio non si vuole ricordare il protocollo completo, si può trovare di nuovo 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 

[...] 
% 

bene, si deve di solito preferiscono un sito che è meno prolissa (di solito serve solo un file statico) rispetto StackOverflow;)

I requisiti minimi (troverete sulla risposta) è:

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

Hello, world! 

''') 

due ritorni sono obbligatorie per il server per ottenere la risposta, ot herwise le attese del browser a tempo indeterminato per le intestazioni

Ma per simulare il comportamento di un server web, non dimenticate di inviare la vostra risposta solo dopo che il browser invia alcuni dati seguiti da due ritorni a capo, di solito si può ottenere quello che invia utilizzando:

% 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 

% 

modo da poter migliorare la vostra routine di test

1

si potrebbe desiderare alla cassa oggetti web http://www.webob.org/

si tratta di una sim progetto leggero per la creazione di richieste e risposte compatibili con http. Si può fare qualsiasi cosa con voi richieste/oggetti di risposta ... O semplicemente delegare il lavoro pesante per WebObjects

campione

>>> 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

ho preso una risposta precedente e modificato il codice per python3 utf -8 e codifica dei byte. Grazie per la risposta originale ha aiutato molto.

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() 
Problemi correlati