首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >剖析Web技术栈(二)

剖析Web技术栈(二)

作者头像
老齐
发布2020-05-14 22:25:42
4860
发布2020-05-14 22:25:42
举报
文章被收录于专栏:老齐教室老齐教室

1 套接字

1.1 基本原理

TCP/IP是一种使用套接字(socket)的网络协议。Socket是包括IP地址(在网络中是唯一的)和端口(对于特定的IP地址是唯一的)的元组,计算机使用IP地址和端口与其他计算机通信。Socket类似文件,可以打开和关闭,也可以读写。Socket编程是一种低级的网络编程,但你需要知道,计算机中提供网络访问的每个软件最终都必须处理Socket(不过,很可能是通过某些库来处理)。

因为我们是从头开始构建,所以要先实现一个小的Python程序,它打开一个socket连接,接收HTTP请求,并返回对这个HTTP请求的响应。由于端口80是一个“低级端口”(一个小于1024的数字),通常没有权限打开那里的socket,所以我将使用端口8080。这暂时不是问题,因为任何端口上都可以提供HTTP服务。

1.2 实现

创建文件server.py并键入下面的代码。是的,输入它,不要只是复制和粘贴,否则你不会学到任何东西。

import socket

# Create a socket instance
# AF_INET: use IP protocol version 4
# SOCK_STREAM: full-duplex byte stream
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Allow reuse of addresses
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# Bind the socket to any address, port 8080, and listen
s.bind(('', 8080))
s.listen()

# Serve forever
while True:
    # Accept the connection
    conn, addr = s.accept()

    # Receive data from this socket using a buffer of 1024 bytes
    data = conn.recv(1024)

    # Print out the data
    print(data.decode('utf-8'))

    # Close the connection
    conn.close()

这个小程序接受8080端口上的连接,并在终端上打印接收到的数据。你可以执行这个程序,然后在另一个终端中运行curl localhost:8080,应该看到类似下面的内容:

$ python3 server.py
GET / HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.65.3
Accept: */*

服务器一直在while循环中运行代码,如果要终止运行,必须使用Ctrl+C来完成。到目前为止还不错,但这还不是一个HTTP服务器,因为它没有发送任何响应;实际上,你应该会从curl接收到一条错误消息,上面写着“curl: (52) Empty reply from server”。

返回标准响应非常简单,我们只需要调用conn.sendall,传递原始字节。最小的HTTP响应包含协议和状态、空行和实际内容,例如:

HTTP/1.1 200 OK

Hi there!

我们的服务器变成

import socket

# Create a socket instance
# AF_INET: use IP protocol version 4
# SOCK_STREAM: full-duplex byte stream
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Allow reuse of addresses
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# Bind the socket to any address, port 8080, and listen
s.bind(('', 8080))
s.listen()

# Serve forever
while True:
    # Accept the connection
    conn, addr = s.accept()

    # Receive data from this socket using a buffer of 1024 bytes
    data = conn.recv(1024)

    # Print out the data
    print(data.decode('utf-8'))

    conn.sendall(bytes("HTTP/1.1 200 OK\n\nHi there!\n", 'utf-8'))

    # Close the connection
    conn.close()

但此时,我们并没有真正响应用户的请求。如果尝试使用不同的curl命令行,如curl localhost:8080/index.htmlcurl localhost:8080/main.css,你总是收到相同的响应。我们应该尝试找到用户请求的资源并将其随同响应的内容返回。

此版本的HTTP服务器正确地提取资源并尝试从当前目录加载它,返回结果或成功或失败

import socket
import re

# Create a socket instance
# AF_INET: use IP protocol version 4
# SOCK_STREAM: full-duplex byte stream
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Allow reuse of addresses
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# Bind the socket to any address, port 8080, and listen
s.bind(('', 8080))
s.listen()

HEAD_200 = "HTTP/1.1 200 OK\n\n"
HEAD_404 = "HTTP/1.1 404 Not Found\n\n"

# Serve forever
while True:
    # Accept the connection
    conn, addr = s.accept()

    # Receive data from this socket using a buffer of 1024 bytes
    data = conn.recv(1024)

    request = data.decode('utf-8')

    # Print out the data
    print(request)

    resource = re.match(r'GET /(.*) HTTP', request).group(1)
    try:
        with open(resource, 'r') as f:
            content = HEAD_200 + f.read()
        print('Resource {} correctly served'.format(resource))
    except FileNotFoundError:
        content = HEAD_404 + "Resource /{} cannot be found\n".format(resource)
        print('Resource {} cannot be loaded'.format(resource))

    print('--------------------')

    conn.sendall(bytes(content, 'utf-8'))

    # Close the connection
    conn.close()

正如你所看到的,这个实现非常简单。如果你使用下面的内容创建一个简单的本地文件,文件名为index.html

<head>
    <title>This is my page</title>
    <link rel="stylesheet" href="main.css">
</head>
<html>
    <p>Some random content</p>
</html>

运行curl localhost:8080/index.html,你将看到文件的内容。此时,你甚至可以使用浏览器打开http://localhost:8080/index.html,你还可以看到页面的标题和内容。Web浏览器是一种能够发送HTTP请求并解释响应内容的软件,只要这些内容是HTML文件(以及许多其他文件类型,如图像或视频)。因此,浏览器可以呈现返回信息的内容。浏览器还负责对渲染所需的缺失资源进行检索。因此,当你在页面的HTML代码中提供指向带有<link><script>标记的样式表或JS脚本的链接时,你也是在指示浏览器为这些文件发送HTTP GET请求。

访问http://localhost:8080/index.html时,server.py的输出是

GET /index.html HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache


Resource index.html correctly served
--------------------
GET /main.css HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: text/css,*/*;q=0.1
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://localhost:8080/index.html
Pragma: no-cache
Cache-Control: no-cache


Resource main.css cannot be loaded
--------------------
GET /favicon.ico HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: image/webp,*/*
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache


Resource favicon.ico cannot be loaded
--------------------

如你所见,浏览器发送大量的HTTP请求,其中包含大量的header信息,自动请求HTML代码中提到的CSS文件,并自动尝试检索网站图标。

1.3 参考资源

这些资源对本节所讨论的主题提供了更详细的信息

  • Python 3 Socket Programming HOWTO[1]
  • HTTP/1.1 Request format[2]
  • HTTP/1.1 Response format[3]

本文例子的源代码可以在这里找到:https://github.com/lgiordani/dissecting-a-web-stack-code/tree/master/1_sockets_and_parsers)

1.4 问题

上面的程序让你有点了点成就感,你从头开始构建了一个项目,然后发现它可以与你每天使用的浏览器等成熟的软件顺利地协同工作。我还认为,有趣的是,像HTTP这样的技术,现在基本上遍布全世界了,但它们的核心非常简单。

在上面的操作中,HTTP的许多特性都没有在简单socket 编程中涉及到。首先,HTTP/1.0在GET之后引入了其他方法,比如POST,它对于今天的网站来说是至关重要的。这些网站的用户通过表单不断地向服务器发送信息。要实现所有这9个HTTP方法,我们需要正确地解析传入的请求并向代码中添加相关函数。

不过,在这一点上,你可能会注意到,我们正在处理协议的许多低级细节,而这些通常不是我们业务的核心。通过HTTP构建一个服务时,我们有足够的知识来正确实现一些代码。这些代码可以简化特定的过程,比如搜索其他网站、购买书籍或与朋友共享图片。我们不想花时间去理解TCP/IP套接字(socket)的微妙之处,也不想为请求——响应协议编写解析器。很高兴看到这些技术的工作原理,但是在日常工作中,我们需要关注更高层次的东西。

由于HTTP是无状态协议,小型HTTP服务器的情况可能会恶化。该协议不提供任何连接两个连续请求的方法,因此可以跟踪通信状态,这是现代互联网的基石。每当我们在一个网站上进行身份验证,并且我们想访问其他页面时,需要服务器记住我们是谁,这意味着要跟踪连接的状态。

长话短说:要成为一个正常运行的HTTP服务器,我们的代码此时应该实现所有HTTP方法和cookies管理,还需要支持其他协议,如Websockets。这些都是些微不足道的任务,所以我们肯定需要在整个系统中添加一些组件,让我们专注于业务逻辑,而不是应用程序协议的低级细节。

阅读链接

参考资料

[1] Python 3 Socket Programming HOWTO: https://docs.python.org/3/howto/sockets.html

[2] HTTP/1.1 Request format: https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5

[3] HTTP/1.1 Response format: https://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 老齐教室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 套接字
    • 1.1 基本原理
      • 1.2 实现
        • 1.3 参考资源
          • 1.4 问题
            • 参考资料
            相关产品与服务
            命令行工具
            腾讯云命令行工具 TCCLI 是管理腾讯云资源的统一工具。使用腾讯云命令行工具,您可以快速调用腾讯云 API 来管理您的腾讯云资源。此外,您还可以基于腾讯云的命令行工具来做自动化和脚本处理,以更多样的方式进行组合和重用。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档