尝试从低级套接字服务器发送HTTP响应

5
这是我的代码:
# Process connections

print('Listening on port', port)
while True:
    c, addr = s.accept()
    print("Got connection from", addr)
    msg = "<html></html>"

    response_headers = {
        'Content-Type': 'text/html; encoding=utf8',
        'Content-Length': len(msg.encode(encoding="utf-8")),
        'Connection': 'close',
    }

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

    response_proto = 'HTTP/1.1'
    response_status = '200'
    response_status_text = 'OK' # this can be random

    # sending all this stuff
    r = '%s %s %s' % (response_proto, response_status, response_status_text)
    c.send(r.encode(encoding="utf-8"))
    c.send(response_headers_raw.encode(encoding="utf-8"))
    c.send('\n'.encode(encoding="utf-8")) # to separate headers from body
    c.send(msg.encode(encoding="utf-8"))

    c.close()

当我在浏览器中访问本地IP时,会出现一个错误页面,上面显示:

ERR_CONNECTION_RESET

我做错了什么?

注意:

完整的代码片段在这里:

import socket

# Create socket object and set protocol
s = socket.socket(
    socket.AF_INET, socket.SOCK_STREAM)

# Get name of local machine
host = socket.gethostname()
port = 80

# Bind
s.bind((host, port))

# Listen and set backlog (?)
s.listen(5)

# Process connections
print('Listening on port', port)
while True:
    c, addr = s.accept()
    print("Got connection from", addr)
    msg = "<html></html>"

    response_headers = {
        'Content-Type': 'text/html; encoding=utf8',
        'Content-Length': len(msg.encode(encoding="utf-8")),
        'Connection': 'close',
    }

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

    response_proto = 'HTTP/1.1'
    response_status = '200'
    response_status_text = 'OK' # this can be random

    # sending all this stuff
    r = '%s %s %s' % (response_proto, response_status, response_status_text)
    c.send(r.encode(encoding="utf-8"))
    c.send(response_headers_raw.encode(encoding="utf-8"))
    c.send('\n'.encode(encoding="utf-8")) # to separate headers from body
    c.send(msg.encode(encoding="utf-8"))

    c.close()

你是否使用过Chrome开发者工具或类似工具进行调查? - John Zwinck
3个回答

1
问题在于HTTP头部需要使用\r\n进行分隔,而不仅仅是\n。请参考RFC 1945 - HTTP/1.0 Sec 2.2RFC 7230 - HTTP/1.1 Syntax and Routing Sec 3。如果更改这个问题,它将解决问题。
import socket

# Create socket object and set protocol
s = socket.socket(
    socket.AF_INET, socket.SOCK_STREAM)

# Get name of local machine
host = "127.0.0.1"
port = 8081

# Bind
s.bind((host, port))

# Listen and set backlog (?)
s.listen(5)

# Process connections
print('Listening on port', port)
while True:
    c, addr = s.accept()
    print("Got connection from", addr)
    msg = "<html><body><h1>This is a test</h1><p>More content here</p></body></html>"

    response_headers = {
        'Content-Type': 'text/html; encoding=utf8',
        'Content-Length': len(msg),
        'Connection': 'close',
    }

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

    response_proto = 'HTTP/1.1'
    response_status = '200'
    response_status_text = 'OK' # this can be random

    # sending all this stuff
    r = '%s %s %s\r\n' % (response_proto, response_status, response_status_text)
    c.send(r)
    c.send(response_headers_raw)
    c.send('\r\n') # to separate headers from body
    c.send(msg.encode(encoding="utf-8"))

    c.close()

我的理解是CRLF('\r\n')是Windows特定的格式。这在Unix系统上能行吗? - dopatraman
此外,这个解决方案不起作用。我能够接收连接,但无法向浏览器发送响应。 - dopatraman
是的,CRLF被HTTP用作行尾标记(参见RFC 7230)。当我运行您的代码时,会出现连接重置的情况。当我改为在每个标题行之间使用\r\n时,它就可以工作了。编辑后的答案包含完整的代码。 - drew010

0
根据ERR_CONNECTION_RESET错误,很可能是您的tcp套接字没有成功监听端口。
我在您的代码中将端口更改为8080,并在输出中写入了一个Hello world,结果得到了正确的HTML页面。
如果您想成功监听80端口,必须使用root用户特权运行您的python脚本:
`sudo python server.py'
这是因为80端口是系统受限制的端口:为什么前1024个端口只限于root用户?

如果它没有在监听,bind 将会失败,客户端也会收到连接被拒绝或最终超时的错误。但不会是重置。这表明它曾经连接过。 - drew010
仅仅是为了补充@drew010的意见,我能够通过命令行连接到运行在80端口的服务器。因此,在该端口上进行连接不是一个问题。 - dopatraman

-1

我假设你能够监听端口80并接受连接,否则你的服务器应该会早早地抛出异常。

我的猜测是你的服务器没有读取客户端的请求,而是简单地发送一些数据并关闭连接,而客户端的请求仍然在套接字缓冲区中。这是一个非常典型的错误,已经在Perl:使用简单HTTP服务器时连接重置HTTP服务器未向WGET、Firefox发送完整文件。对等方重置连接?尝试连接到本地主机套接字服务器时Web浏览器显示“连接被重置”用PHP编写的超级简单的HTTP套接字服务器表现出意外行为以及使用fork和dup的简单http通用服务器中得到了解答。


这些链接都没有详细说明如何解决问题。 - dopatraman
@dopatraman:他们都描述了问题所在,即您没有阅读客户端的请求,而只是发送响应并关闭带有未读数据的套接字。解决方案显然是实际从客户端读取请求。难道您没有理解这种关联或者不知道如何读取请求吗? - Steffen Ullrich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接