如何正确使用套接字库发送HTTP响应与Python?

如何正确使用套接字库发送HTTP响应与Python?

问题描述:

我有一个用Python编写的非常简单的网络服务器。它监听端口13000,如果在浏览器中打开http://localhost:13000,如何才能使其传递一个简单的“Hello World”网页?如何正确使用套接字库发送HTTP响应与Python?

就在那里是我的代码:

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

正如你所看到的,我不完全知道如何真正发回的网页?

我只需要使用socket库。

编辑:问题不是我不知道如何制定HTTP响应,我不知道如何实际使它显示在我的浏览器!它只是继续旋转/加载。

根据问题的变化更新

可能的话,它不断旋转,因为由于缺少的的Content-LengthConnection头组合,浏览器可能会以为这是Connection: keep-alive,所以它继续接受永远从你的服务器数据。尝试发送Connection: close,并通过实际Content-Length看看是否有帮助。


这不会做你期望的吗? :)

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

有关更详细的说明,请参阅description of HTTP protocol

+1

是的,但...浏览器只是“旋转”,什么也没有显示? – antonpug 2012-04-11 21:29:06

+0

我已经更新了代码示例以保证工作100%:)我希望您能找到有用的标头处理的基本原则,但我建议您不要依赖这种代码并实现全功能的HTTP请求解析器。 – toriningen 2012-04-11 21:55:24

+1

好吧,你已经做出了相当完整的回答......虽然我认为它仍在旋转的原因(直到超时)是因为它正在等待双倍“\ n”。但至少,你的代码示例是一个很好的代码片段,以防万一;) – zmo 2012-04-11 22:25:25

发送回是这样的:

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

然后实际的HTML代码。确保在Content-Type行之后和html之前有一个换行符。

或者,如果你只是不想记得完整协议,你可以找到它再次使用:

% 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 

[...] 
% 

好,你应该通常喜欢一个网站,看起来更简洁(通常只有一个服务静态文件)比计算器;)

的最低要求(你会发现答案)是:

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

Hello, world! 

''') 

2回报是强制性的服务器得到的答案,OT herwise浏览器无限期地等待头

但要模仿一个Web服务器的行为,不要忘记把你的答案浏览器发送你的一些数据,然后两个回车后,通常你能得到什么利用发送:

% 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 

% 

这样你就可以提高你的测试例程

您可能要结帐Web对象http://www.webob.org/

这是一个SIM卡用于创建与HTTP兼容的请求和响应的轻量级项目。你可以做的只是与你的请求/响应对象的东西......或者只是委托繁重到的WebObjects

样品

>>> from webob import Response 
>>> res = Response() 
>>> res.status 
'200 OK' 
>>> res.headerlist 
[('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')] 
>>> res.body 
'' 

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

我把前面的回答和编辑的Python3 UTF编码-8和字节编码。感谢您的原始答案,它帮助了很多。

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