用 Python 实现简易 Web 服务器!

基于python 自己动手写一个简单的web服务器 1. 前言 如今一提到服务器首先就想到 apache tomcat nginx等。虽然这些服务器很优秀。但是对于我们平时拿来练手的一些小项目来说却是大材小用,杀鸡用牛刀,而且上述主流服务器配置起来也略嫌麻烦。俗话说自己动手丰衣足食,今天我们就来实现一个简单的静态web服务器。 2. http协议 谈到浏览器,服务器时,作为计算机行业从业者,首先想到的就是www、http、https、html等等,... 阅读详情

花下猫语: 上篇文章推荐了一本神书《500 Lines or Less》( 点此阅读 ),有几位读者留言问是否有中文版。很遗憾,还没有。不过我在文中也说了,Github 上有个翻译计划,已收录了 10 几篇译文。这些文章的代码量虽然不多,但是因为讲解的内容很详细,所以篇幅都很长。今天就先给大家分享其中一篇吧。如果你感兴趣的话,上篇文章末尾附了链接,可去查看。

 

 

 

Python资源共享群:484031800

图 | 宫崎骏电影《侧耳倾听》

作者简介:Greg Wilson 是 Software Carpentry(一个科学家和工程师的计算技巧速成班)的创始人。他在工业界和学术界工作了三十余年,是好几本计算相关图书的作者或编辑,包括了 2008 年 Jolt 图书奖得主 Beautiful Code 和 开源软件架构 的前两卷。1993 年,Greg 获得了爱丁堡大学的计算机博士学位。

简介

在过去的二十多年里,网络改变了社会的各个方面,但它的核心却改动不多。大多数系统仍然遵循着 Tim Berners-Lee 在 25 年前所制定的规则。尤其是,大多数 Web 服务器仍旧以相同的方式处理着相同的数据,一如既往。

本章节将探讨它们如何实现。与此同时,本章节还将探讨开发者如何创建增加新特性而不需要重写的软件系统。

背景

几乎所有的网络程序都运行在一类叫做 互联网协议(IP)的通信标准上。这类协议中,我们涉及的是传输控制协议(TCP/IP),该协议使得计算机间通信类似于读写文件。

程序通过套接字使用 IP 协议进行通信。每个套接字是点对点通信信道的一端,正如电话机是一次电话通信的一端。一个套接字包含着一个 IP 地址,该地址确定了一台确定的机器和该机器上的一个端口号。IP 地址包含了四个八位数字,比如 174.136.14.108 ;域名系统将这些数字与字符相匹配,比如 aosabook.org ,以便于记忆。

端口号码是 0 - 65535 之间的一个随机数,唯一确定了主机上的套接字。(如果说 IP 地址像一家公司的电话号码,那么端口号就像是分机号。)端口 0 - 1023 预留给操作系统使用;任何人都可以使用剩下的端口。

超文本传输协议(HTTP)描述了程序通过 IP 协议交换数据的一种方法。HTTP 协议刻意设计得简单: 客户端通过套接字发送一个请求,指定请求的东西,服务器在响应中返回一些数据(如下图)。该数据或许复制自硬盘上的文件,或许由程序动态生成,或是二者的混合。

 

 

 

 

关于 HTTP 请求,最重要的地方在于,它仅由文本组成。任何有意愿的程序都可以对其进行创建或解析。不过,为了被正确地解析,文本中必须包含下图所展示的部分。

 

 

 

 

(注: 'sp':空格, 'cr lf':换行) HTTP 方法大多是 GET(请求信息)或者 POST(提交表单或上传文件)。统一资源定位器(URL)确定了客户端所请求的文件路径,一般位于硬盘上,比如 /research/experiments.html , 但是(接下来才是关键),如何处理完全取决于服务器。HTTP 版本一般是 "HTTP/1.0" 或 "HTTP/1.1" ; 二者之间的差异对我们来说并不重要。

HTTP 首部(Headers)是一组键值对,如同下面这三行:

Accept: text/html
Accept-Language: en, fr
If-Modified-Since: 16-May-2005

不同于哈希表中的键,HTTP 首部中,键可以出现任意多次。这将允许请求做一些事,例如指定愿意接收多种类型的内容。

最后,请求的主体是与请求关联的任何数据。这个应用于通过表单提交数据,上传文件等。首部的末尾和主体的开头之间必须由一个空行,以声明首部的结束。

首部中, Content-Lenght 告诉服务器在请求主体中有多少字节需要被读取。

HTTP 响应的格式与 HTTP 请求类似:

 

 

 

 

版本号,首部,主体有着相同的格式和意义。状态码是一个数字,用来指示在处理请求时所发生的事情: 200 意味着 "一切工作正常",404 意味着 "没有找到",其他状态码也分别有着各自的含义。状态词以易读的形式重复着上述信息,比如 "一切正常" 或是 "没有找到"。

本节中,我们只需要了解关于 HTTP 的两件事情。

第一,HTTP 是无状态的: 每个请求自行处理,服务器在两个请求之间不会记住任何东西。如果应用想要跟踪一些信息,比如用户的身份,它必须自己实现。

实现的方法通常使用 cookie, 这是服务器发送到客户端的短字符串,之后由客户端返回给服务器。当用户执行一些函数,需要在多个请求之间保存状态时,服务器会创建一个新的 cookie,将它存储在数据库中,然后发送给浏览器。每次浏览器返回 cookie,服务器通过 cookie 寻找关于用户行为的信息。

我们需要了解的第二点是,可以填充参数以提供更多的信息。比如说,如果我们使用搜索引擎,我们需要指定关键词。我们可以将这些附加到 URL 路径中,但更应该是在 URL 中附加参数。我们在 URL 后附加 '?' ,之后是以 '&' 分隔的键值对('key=value')。比如说,URL http://www.google.ca?q=Python 要求谷歌查询关于 Python 的页面: 键是字母 'q',值是 'Python'。长一点的查询 http://www.google.ca/search?q=Python&client=Firefox ,告诉谷歌我们在使用 Firefox,诸如此类。我们可以传输任何参数,不过,哪些参数需要注意,如何解释这些参数,完全取决于网站上运行的程序。

当然,如果 '?' 和 '&' 用作特殊字符,必然有方法加以避免,正如必须有方法将一个双引号字符放置在由双引号分隔的字符串内。URL 编码标准使用 '%' 后跟两位代码表示特殊字符,并使用 '+' 字符替代空格。因此,我们使用 URL http://www.google.ca/search?q=grade+%3D+A%2B 在谷歌中搜索 "grade = A+"(注意空格)。

打开套接字,构建 HTTP 请求,解析响应极其乏味,因此大多数用户使用库来做大部分工作。Python 附带了一个这样的库,叫做 urllib2 (因为它是之前的库 urllib 的代替者),但是它暴露了许多大多数用户不关心的东西。相比于 urllib2 , Requests 库是一个更加易于使用的选择。接下来是一个例子,使用 Requests 下载来自 AOSA book 站点的一个页面。

import requests
response = requests.get('http://aosabook.org/en/500L/web-server/testpage.html')
print 'status code:', response.status_code
print 'content length:', response.headers['content-length']
print response.text
status code: 200
content length: 61
<html>
  <body>
    <p>Test page.</p>
  </body>
</html>

requests.get 向服务器发送一个 HTTP GET 请求,返回一个包含响应的对象。该对象的 status_code 是响应的状态码;它的 content_length 是响应数据的字节数; text 是真正的数据(在这个例子中,是一个 HTML 页面)。

Hello, Web

现在,我们已经为编写我们第一个简单的 Web 服务器做好了准备。基本思想很简单:

  1. 等待用户连接我们的站点并发送一个 HTTP 请求;
  2. 解析请求;
  3. 计算出它所请求的;
  4. 获取数据(或动态生成);
  5. 格式化数据为 HTML;
  6. 返回数据。

步骤 1, 2, 6 都是从一个应用程序到另一个,Python 标准库有一个 'BaseHTTPServer' 模块,为我们实现这部分。我们只需要关心步骤 3 - 5,这也是我们在下面的小程序中所做的。

import BaseHTTPServer
class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    '''Handle HTTP requests by returning a fixed 'page'.'''
    # Page to send back.
    Page = '''\
<html>
<body>
<p>Hello, web!</p>
</body>
</html>
'''
    # Handle a GET request.
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-Type", "text/html")
        self.send_header("Content-Length", str(len(self.Page)))
        self.end_headers()
        self.wfile.write(self.Page)
#----------------------------------------------------------------------
if __name__ == '__main__':
    serverAddress = ('', 8080)
    server = BaseHTTPServer.HTTPServer(serverAddress, RequestHandler)
    server.serve_forever()

库里面的 BaseHTTPRequestHandler 类负责解析传进来的 HTTP 请求,并判断请求包含的方法。如果方法是 GET, 类将调用 do_GET 方法。我们的类 RequestHandler 重写了该方法以动态生成一个简单的页面: 文本页面存储在类级别变量中,我们将在发送给客户端 200 响应码,首部 Content-Type 字段以告诉客户端将返回的数据解析为 HTML,页面长度之后发送它。( end_headers 方法调用 插入空行以分隔首部和页面本身。)

然而 RequestHandler 并非故事的所有: 我们仍需要最后的三行来真正启动服务器。第一行以一个元组定义了服务器地址: 空字符串表示 "在当前主机上运行", 8080 标识了端口。接下来我们以地址和我们的请求处理类名作为参数创建了 BaseHTTPServer.HTTPServer 的一个实例,然后要求它一直运行(这意味着它将一直运行直至我们使用 'Ctrl - C' 杀掉它)。

如果我们在命令行中运行这个程序,它将不会显示任何东西:

$ python server.py

如果我们在浏览器中访问 http://localhost:8080 , 我们将在浏览器中看到:

Hello, web!

同时在 shell 中:

127.0.0.1 - - [24/Feb/2014 10:26:28] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [24/Feb/2014 10:26:28] "GET /favicon.ico HTTP/1.1" 200 -

第一行很简单: 因为我们没有要求一个特定的文件,浏览器便请求 '/'(任何正常工作服务器的根目录)。第二行出现是因为浏览器自动发送第二个请求,请求一个叫做 '/favicon.ico' 的图像文件,如果存在,将在地址栏显示为一个图标。

展示一些值

让我们修改我们的 Web 服务器以展示一些包含在 HTTP 请求中的值。(在调试时,我们会经常这样做,所以不妨先做一些练习。)为了保持代码整洁,我们将分别创建网页生成和发送。

class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    # ...page template...
    def do_GET(self):
        page = self.create_page()
        self.send_page(page)
    def create_page(self):
        # ...fill in...
    def send_page(self, page):
        # ...fill in...

send_page 比之前的多很多。

    def send_page(self, page):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.send_header("Content-Length", str(len(page)))
        self.end_headers()
        self.wfile.write(page)

我们想要展示的页面的模板只是一个字符串,包含着一个有一些占位符的表格。

    Page = '''\
<html>
<body>
<table>
<tr>  <td>Header</td>         <td>Value</td>          </tr>
<tr>  <td>Date and time</td>  <td>{date_time}</td>    </tr>
<tr>  <td>Client host</td>    <td>{client_host}</td>  </tr>
<tr>  <td>Client port</td>    <td>{client_port}s</td> </tr>
<tr>  <td>Command</td>        <td>{command}</td>      </tr>
<tr>  <td>Path</td>           <td>{path}</td>         </tr>
</table>
</body>
</html>
'''

填充表格的方法如下:

    def create_page(self):
        values = {
            'date_time'   : self.date_time_string(),
            'client_host' : self.client_address[0],
            'client_port' : self.client_address[1],
            'command'     : self.command,
            'path'        : self.path
        }
        page = self.Page.format(**values)
        return page

该程序的主体并没有改变:正如之前,它以地址和请求处理程序作为参数,创建了一个 HTTPServer 类的实例,然后一直处理请求。如果我们运行它,然后用浏览器发送一个请求给 http://localhost:8000/something.html ,我们将得到:

  Date and time  Mon, 24 Feb 2014 17:17:12 GMT
  Client host    127.0.0.1
  Client port    54548
  Command        GET
  Path           /something.html

注意到,我们没有得到一个 404 错误,即使 something.html 页面并不存在。这是因为 Web 服务器只是一个程序,当它收到请求时,会做它所需要的任何事情: 返回之前请求提到的文件,提供一个随机选取的维基百科页面,或者我们编程时让它做的任何事情。

提供静态页面

显然,接下来的步骤是提供静态文件,取代动态生成。我们将重写 do_GET 。

    def do_GET(self):
        try:
            # Figure out what exactly is being requested.
            full_path = os.getcwd() + self.path
            # It doesn't exist...
            if not os.path.exists(full_path):
                raise ServerException("'{0}' not found".format(self.path))
            # ...it's a file...
            elif os.path.isfile(full_path):
                self.handle_file(full_path)
            # ...it's something we don't handle.
            else:
                raise ServerException("Unknown object '{0}'".format(self.path))
        # Handle errors.
        except Exception as msg:
            self.handle_error(msg)

上述方法假设允许程序使用所在路径(就是使用 os.getcwd 所得到的)下的任意文件提供服务。它会结合 URL 提供的路径(总是以 '/' 开始, BaseHTTPServer 会自动将它放入 self.path ),以获取用户想要的文件的路径。如果文件不存在,或者路径并不指向文件,上述方法将通过获取并抛出异常来报告错误。另一方面,如果路径匹配到文件, do_GET 方法将调用辅助方法 handle_file 来读取并返回内容。辅助方法仅读取文件,然后调用 send_content 将文件内容返回给客户端:

    def handle_file(self, full_path):
        try:
            with open(full_path, 'rb') as reader:
                content = reader.read()
            self.send_content(content)
        except IOError as msg:
            msg = "'{0}' cannot be read: {1}".format(self.path, msg)
            self.handle_error(msg)

请注意,我们以二进制方式打开文件—由 'rb' 中 'b' 标识,这样 Python 不会改变看起来像 Windows 行结束的字节序列。同时,请注意,在使用文件提供服务时,将整个文件读入内存在真实生活中并不合适,视频文件大小可能是好几G。处理上述情况已经超出了本章的范围。我们接下来编写错误处理方法和错误处理页面模板来结束本节。

    Error_Page = """\
        <html>
        <body>
        <h1>Error accessing {path}</h1>
        <p>{msg}</p>
        </body>
        </html>
        """
    def handle_error(self, msg):
        content = self.Error_Page.format(path=self.path, msg=msg)
        self.send_content(content)

如果我们不仔细观察,程序似乎正常运行。问题在于它总是返回 200 状态码,即使所请求的页面并不存在。是的,返回的页面包含着错误信息,但因为浏览器读不懂英文,它并不知道请求实际失败了。为了使错误明确,我们需要修改 handle_error 和 send_content 如下:

    # Handle unknown objects.
    def handle_error(self, msg):
        content = self.Error_Page.format(path=self.path, msg=msg)
        self.send_content(content, 404)
    # Send actual content.
    def send_content(self, content, status=200):
        self.send_response(status)
        self.send_header("Content-type", "text/html")
        self.send_header("Content-Length", str(len(content)))
        self.end_headers()
        self.wfile.write(content)

注意,当文件找不到时,我们并没有抛出 ServerException 异常,而是生成一个错误页面。 ServerException 意味着服务器内部错误,即,我们弄错了。另一方面,当用户遇到错误时,此处即,请求了一个不存在的文件的 URL 时,由 handle_error 生成错误页面。

目录列表

下一步,我们将教会服务器,在 URL 中,路径代表目录而不是文件时,展示一个目录内容的列表。我们甚至可以更进一步,让程序在目录中寻找 index.html 文件来展示。不过在 do_GET 中构建这些方法或许是一个错误,因为生成的方法将会是很多 if 语句混杂在一起来控制特殊行为。正确的方案是退一步,解决一个更一般的问题:弄清楚如何处理一个 URL。

    def do_GET(self):
        try:
            # Figure out what exactly is being requested.
            self.full_path = os.getcwd() + self.path
            # Figure out how to handle it.
            for case in self.Cases:
                handler = case()
                if handler.test(self):
                    handler.act(self)
                    break
        # Handle errors.
        except Exception as msg:
            self.handle_error(msg)

第一步完全相同:弄清楚请求的完整路径。之后,代码看起来就不同了。这个版本循环遍历一组存储在列表中的情况,而不是一组内嵌的测试。每种情况都是一个有着两个方法的对象, test 告诉我们是否能够处理请求, act ,实际上进行处理。一旦我们发现正确的情况,我们让它处理请求并跳出循环。这三个类重复之前的服务器的行为:

class case_no_file(object):
    '''File or directory does not exist.'''
    def test(self, handler):
        return not os.path.exists(handler.full_path)
    def act(self, handler):
        raise ServerException("'{0}' not found".format(handler.path))
class case_existing_file(object):
    '''File exists.'''
    def test(self, handler):
        return os.path.isfile(handler.full_path)
    def act(self, handler):
        handler.handle_file(handler.full_path)
class case_always_fail(object):
    '''Base case if nothing else worked.'''
    def test(self, handler):
        return True
    def act(self, handler):
        raise ServerException("Unknown object '{0}'".format(handler.path))

这是我们在 RequestHandler 类开头,如何构建事件处理程序列表:

class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    '''
    If the requested path maps to a file, that file is served.
    If anything goes wrong, an error page is constructed.
    '''
    Cases = [case_no_file(),
             case_existing_file(),
             case_always_fail()]
    ...everything else as before...

现在,表面上我们的服务器更加复杂了,而不是简洁。文件从 74 行变成 99 行,并有了一个额外的,没有任何新功能的间接层。不过当我们回到本节最初提出的任务::教会服务器为一个目录请求,在 index.html 存在时返回 index.html , 不存在时返回目录内容列表,好处随之出现。前者的处理程序如下:

class case_directory_index_file(object):
    '''Serve index.html page for a directory.'''
    def index_path(self, handler):
        return os.path.join(handler.full_path, 'index.html')
    def test(self, handler):
        return os.path.isdir(handler.full_path) and \
               os.path.isfile(self.index_path(handler))
    def act(self, handler):
        handler.handle_file(self.index_path(handler))

接下来,辅助方法 index_path 构造 index.html 文件的路径;将它放进事件处理程序以防止主类 RequestHandler 的杂乱。 test 检查路径是否是一个包含 index.html 页面的目录, act 要求主请求处理程序返回这个页面。 RequestHandler 所需的唯一变化是将一个 case_directory_index_file 类加入我们的 Cases 列表:

    Cases = [case_no_file(),
             case_existing_file(),
             case_directory_index_file(),
             case_always_fail()]

要是目录不包含一个 index.html 页面呢? test 和之前的一个一致,可是, act 方法呢?它应该变成什么样?

class case_directory_no_index_file(object):
    '''Serve listing for a directory without an index.html page.'''
    def index_path(self, handler):
        return os.path.join(handler.full_path, 'index.html')
    def test(self, handler):
        return os.path.isdir(handler.full_path) and \
               not os.path.isfile(self.index_path(handler))
    def act(self, handler):
        ???

似乎我们把自己逼进了墙角。逻辑上讲, act 方法应该创建并返回目录列表,但我们现存的代码不允许那样:: RequestHandler.do_GET 调用 act 方法,却不期望它返回一个值。让我们暂时为 RequestHandler 增加一个方法以生成目录列表,然后使用事件处理器的 act 方法调用它:

class case_directory_no_index_file(object):
    '''Serve listing for a directory without an index.html page.'''
    # ...index_path and test as above...
    def act(self, handler):
        handler.list_dir(handler.full_path)
class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    # ...all the other code...
    # How to display a directory listing.
    Listing_Page = '''\
        <html>
        <body>
        <ul>
        {0}
        </ul>
        </body>
        </html>
        '''
    def list_dir(self, full_path):
        try:
            entries = os.listdir(full_path)
            bullets = ['<li>{0}</li>'.format(e)
                for e in entries if not e.startswith('.')]
            page = self.Listing_Page.format('\n'.join(bullets))
            self.send_content(page)
        except OSError as msg:
            msg = "'{0}' cannot be listed: {1}".format(self.path, msg)
            self.handle_error(msg)

CGI 协议

理所当然,大多数人不想为了添加新的功能而编辑 web 服务器的源代码。为了将他们从编辑源码拯救出来,服务器一般都支持一种叫做公共网关接口(CGI)的机制,它为 web 服务器提供了一个标准的方式来运行外部程序,以响应请求。例如,假设我们想要服务器可以在一个 HTML 页面上展示本地时间,我们可以在一个只有几行代码的独立程序中实现:

from datetime import datetime
print '''\
<html>
<body>
<p>Generated {0}</p>
</body>
</html>'''.format(datetime.now())

为了让 web 服务器运行这个程序,我们添加了下面的事件处理器:

class case_cgi_file(object):
    '''Something runnable.'''
    def test(self, handler):
        return os.path.isfile(handler.full_path) and \
               handler.full_path.endswith('.py')
    def act(self, handler):
        handler.run_cgi(handler.full_path)

test 很简单::文件路径是否以 .py 结尾?是的话, RequestHandler 将运行上面的独立程序。

    def run_cgi(self, full_path):
        cmd = "python " + full_path
        child_stdin, child_stdout = os.popen2(cmd)
        child_stdin.close()
        data = child_stdout.read()
        child_stdout.close()
        self.send_content(data)

这是非常不安全的: 如果有人知道了我们服务器上一个 Python 文件的路径,我们将不得不允许他运行该程序,而没有考虑,它有权限访问哪些数据,它是否包含一个死循环,或者二者之外。

扫清上述隐患,核心理念很简单:

  1. 在一个子进程中运行该程序。
  2. 捕获子进程发送到标准输出的一切。
  3. 返回给发起请求的客户端。

完整的 CGI 协议比这更丰富,它允许 URL 中存在参数,服务器会将它们传入正在运行的程序,但这些细节并不会影响系统的整体架构…

事情再一次变得复杂。 RequestHandler 最初有一个方法, handle_file ,来处理内容。我们现在在 list_dir 和 run_cgi 中增加两个特殊的情况。这三个方法无所谓在哪儿,因为它们主要由其他方法调用。

解决办法很简单: 为我们所有的事件处理器创建一个父类,当且仅当方法在多个处理器间共享时,将他们移入父类中。这样做了之后, RequestHandler 类就像下面这样:

class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    Cases = [case_no_file(),
             case_cgi_file(),
             case_existing_file(),
             case_directory_index_file(),
             case_directory_no_index_file(),
             case_always_fail()]
    # How to display an error.
    Error_Page = """\
        <html>
        <body>
        <h1>Error accessing {path}</h1>
        <p>{msg}</p>
        </body>
        </html>
        """
    # Classify and handle request.
    def do_GET(self):
        try:
            # Figure out what exactly is being requested.
            self.full_path = os.getcwd() + self.path
            # Figure out how to handle it.
            for case in self.Cases:
                if case.test(self):
                    case.act(self)
                    break
        # Handle errors.
        except Exception as msg:
            self.handle_error(msg)
    # Handle unknown objects.
    def handle_error(self, msg):
        content = self.Error_Page.format(path=self.path, msg=msg)
        self.send_content(content, 404)
    # Send actual content.
    def send_content(self, content, status=200):
        self.send_response(status)
        self.send_header("Content-type", "text/html")
        self.send_header("Content-Length", str(len(content)))
        self.end_headers()
        self.wfile.write(content)

我们的事件处理程序的父类如下:

class base_case(object):
    '''Parent for case handlers.'''
    def handle_file(self, handler, full_path):
        try:
            with open(full_path, 'rb') as reader:
                content = reader.read()
            handler.send_content(content)
        except IOError as msg:
            msg = "'{0}' cannot be read: {1}".format(full_path, msg)
            handler.handle_error(msg)
    def index_path(self, handler):
        return os.path.join(handler.full_path, 'index.html')
    def test(self, handler):
        assert False, 'Not implemented.'
    def act(self, handler):
        assert False, 'Not implemented.'

现存文件处理程序如下(随机选择一个例子):

class case_existing_file(base_case):
    '''File exists.'''
    def test(self, handler):
        return os.path.isfile(handler.full_path)
    def act(self, handler):
        self.handle_file(handler, handler.full_path)

讨论

原生的代码和重构后版本的差异反映了两个很重要的观念。第一个是把类看作是相关服务的一个集合。 RequestHandler 和 base_case 并不作出决定或采取行动;它们只为其他做这些事的类提供工具。

第二个是可拓展性: 人们可以通过写一个外部的 CGI 程序,或者增加一个事件处理类,来为我们的 web 服务器增加新的功能。后者需要在 RequestHandler 中改变一行(将事件处理器插入事件列表),但我们可以让 web 服务器读一个配置文件,并从中加载事件处理类来摆脱上述改变。在这两种情况下,他们可以忽略大部分低层次细节,正如 BaseHTTPRequestHandler 类的开发者允许我们忽略处理套接字连接的细节和解析 HTTP 请求。

这些观念通常很有用;试试看你能否找到方法,将他们应用到你自己的项目中。

  1. 我们将要在本章中用到好几次 handle_error ,包括一些 404 状态码不合适的情况。在你阅读的过程中,试着去思考,你将如何扩展这个项目,能使得状态码可以很轻松地在每种情况下提供。
  2. 我们的代码也使用了 popen2 库函数,为了更好的支持子流程模块它被弃用。不过, popen2 用在这个例子中,的确是更少分散注意力的工具。 
使用python搭建一个简易的Web服务 简单的说,web服务器就是机器上监听了本地的IP和端口的一个进程,当有客户的请求进来时,它会响应客户的请求并返回相应的信息。客户端和服务端之间的通信基于HTTP协议,客户端可以是浏览器或者任何支持HTTP的应用。我们平时访问网页或手机APP里接受服务端的数据,都是通过这样的方式。下面这张图是一个客户端和服务端交互的过程 用户在浏览器输入网址向web服务器发出一个请求,web服务器收到请求后返回Hello, world!给浏览器,用户就能在浏览器上看到文字。我们来用Python实现这样一个简单的web服务器。 阅读详情

相关推荐

Python实现简易HTTP Server:打造你自己的Web服务器

在上面的代码中,我们定义了一个MyHandler类,继承了BaseHTTPRequestHandler类。本文将介绍如何使用Python实现一个简易的HTTP服务器,帮助您快速打造自己的Web服务器。首先,我们需要导入Python内置的http.server模块,这个模块提供了一个简单的Web服务器框架。在实际应用中,我们需要更多的功能,比如处理不同的HTTP方法、处理POST请求、读取静态文件等等。但是,本文仅介绍了一个简单的例子,希望能够帮助您入门Python Web服务器的开发。

. 1426

如何使用 Python 实现简单的 Web 服务器?

为了实现一个简单的Web服务器,Python提供了多种方法。对于快速原型设计和学习目的来说,最简单的方法之一是使用内置的模块。然而,在实际开发中,更常见的做法是使用像Flask或Django这样的框架来构建更为复杂的应用程序。下面我将介绍如何用Python创建一个基本的Web服务器,并提供一些实用建议和注意事项。我们将从最基础的开始,然后逐步深入到更复杂的场景。

程序员学习园地。 2177

Python实现简单的Web服务器

互联网在过去20年里已经大大地改变了我们的生活方式,影响着社会。但是反观互联网,它的基础-web的核心原理并没有改变多少。大部分web系统仍旧遵守 Tim Berners-Lee 20 多年前提出的 W3C 标准,大部分web服务器接收的信息格式与接收的方式与过去并无二致。

全网120W+关注AI拉呱,专注人工智能以及科技前沿! 3418

2024年最新【面试必会,Python写一个简易的web服务器(1),Python面试项目

不知道你们用的什么环境,我一般都是用的Python3.6环境和pycharm解释器,没有软件,或者没有资料,没人解答问题,都可以免费领取(包括今天的代码),过几天我还会做个视频教程出来,有需要也可以领取~给大家准备的学习资料包括但不限于:Python 环境、pycharm编辑器/永久激活/翻译插件python 零基础视频教程Python 界面开发实战教程Python 爬虫实战教程Python 数据分析实战教程python 游戏开发实战教程Python 电子书100本。

2401_84132393的博客 851

从零开始:构建简易Web服务器

HTTP(HyperText Transfer Protocol,超文本传输协议)是一种用于分布式、协作式和超媒体信息系统的应用层协议。它被设计用来在客户端(通常是Web浏览器)和服务器之间传输超文本内容。HTTP通过使用URL(统一资源定位符)来标识资源,并通过HTTP请求和响应的方式实现资源的请求和获取。HTTP协议作为Web服务器的核心,承担着客户端和服务器之间信息交流的重任。它定义了请求和响应的标准格式,确保了不同类型的客户端和服务器能以一种统一的方式进行通信。

weixin_32921023的博客 801

firefly基于python的游戏服务器框架

Firefly是免费、开源、稳定、快速扩展、能 “热更新”的分布式游戏服务器端框架,采用Python编写,基于Twisted框架开发。它包括了开发框架和数据库缓存服务等各种游戏服务器基础服务,节省大量游戏开发的工作时间,真正做到让使用者把精力放在游戏玩法逻辑上。用它可以搭建自定义的分布式架构,只需要修改相应的配置文件即可。 优势特性 采用单线程多进程架构,支持自定义的分布式架构; 方便的服务器扩展机制,可快速扩展服务器类型和数量; 与客户端采用TCP长连接,无需考虑粘包等问题; 封装数据缓存服务; 可实现实时热更新数据以及游戏逻辑,客户端玩家无感觉; 有几十个基础游戏玩法系统模块提供组装使用(v1.3.0提供);

Python写一个简易的web服务器

现在,我们准备写我们的第一个简单的Web服务器。基本思想非常简单:1.等待有人来连接我们的服务器,发送HTTP请求;2.解析请求;3.找出它的要求;4.获取数据(或动态生成它);5.把数据格式化为HTML;6.回发。从一个应用程序到另一个,步骤1,2和6是相同的,所以Python标准库有一个名为BaseHTTPServer的模块为我们做这些步骤。

2401_86984390的博客 1493

SAP生产版本导入避坑大全:从‘黄灯’到‘绿灯’,详解CM_FV_MKAL_CONSISTENCY_CHECK检查函数

本文深入解析SAP生产版本导入过程中的常见问题,特别是通过`CM_FV_MKAL_CONSISTENCY_CHECK`检查函数从‘黄灯’异常到‘绿灯’正常的修复方法。涵盖静态与动态校验机制、典型异常场景诊断及批量导入最佳实践,帮助SAP运维人员高效解决生产版本管理难题。

weixin_30568591的博客 389

Python实例题:Pvthon实现简单的Web服务器

Pvthon实现简单的Web服务器。

shangzhiqi的博客 520

别再写前端了!用Gradio 3.x + HuggingFace Spaces,5分钟把你的Python算法变成网页应用

本文介绍如何利用Gradio 3.x和HuggingFace Spaces快速将Python算法转化为网页应用,无需前端开发经验。通过5行代码和简单的Git命令,即可实现专业级Web界面部署,大幅提升算法展示效率。特别适合算法工程师和数据科学家快速分享模型成果。

weixin_33443333的博客 332

Unity URP管线下的Highlight Plus插件,从导入到实现物体高亮的保姆级避坑指南

本文详细解析了在Unity URP管线中使用Highlight Plus插件实现物体高亮的完整流程与常见问题解决方案。从URP环境配置、插件导入避坑到相机设置优化,提供了一套完整的保姆级指南,帮助开发者高效解决高亮效果不显示等典型问题,并包含移动端性能优化与复杂场景管理的高级技巧。

weixin_27379781的博客 408

【从零学习python 】88. WSGI接口详解:实现简单高效的Web开发

本文介绍了WSGI(Web Server Gateway Interface)接口的定义和使用。WSGI是一种简单而强大的接口标准,通过实现一个函数即可响应HTTP请求。文章详细阐述了WSGI接口的结构和使用方法,并介绍了如何从environ字典对象中获取HTTP请求信息、构建HTML内容并发送HTTP响应Header。此外,还介绍了WSGI服务器的选择和使用,其中提到了Python内置的wsgiref模块作为参考实现。

全栈若城,专注知识分享 1140

【Boost5】Boost变换器的输入电容

节点的电流有3个,一个是来自电源输入的,由于我们把Vin看成理想电源以及线路的电感的存在,它可以近似看作是恒定的电流源Iin,一个是流向输入电容Cin的电流Icin,另外一个节点是电感L电流IL。电容电流波形在整个开关周期内的平均值为0,充电和放电在一个周期中各占1/2,在图中充电部分面积和放电部分面积相等,那么在充电或放电的电荷量Q=I*t,从图中可以看出,在一个周期内电容电流变化量是ΔIL,那在电容在充电1/2T周期中电流的变化量是1/2ΔIL。输入电容上的ESR也会随着电流变化,影响输入电压的变化。

qq_43416206的博客 3350

【Socket编程】Python实现简易Web服务器

利用Python,我们可以搭建一个简单的Web服务器,这里我参考了: https://ruslanspivak.com/lsbaws-part1/

mick_seu的博客 7233

rk3568 android 11.0 userdata 改为ext4格式

功能:解决设备在恢复出厂设置进行擦除数据的时候卡住问题。 1.修改userdata分区为ext4格式 diff --git a/device/rockchip/common/scripts/fstab_tools/fstab.in b/device/rockchip/common/scripts/fstab_tools/fstab.in index 583e75579d..036336835d 100755 --- a/device/rockchip/common/scripts/fstab_tools/

chgh1306的专栏 2578

PentAGI自动渗透测试平台搭建与使用

但是PentAGI的缺陷也比较明显,一个是自动化程度高带来过程的介入困难,整个渗透的过程都交给AI自动决策,无法进行人工调整,耗时较长;从上述过程来看PentAGI似乎是一个比较“可用”的AI自动化渗透测试平台,但是在继续等待测试的过程中,PentAGI的一些缺点也暴露了出来,一个是测试的过程非常漫长,因为PentAGI是使用配置大模型API key的方式全自动运行,支持openai、deepseek等主流LLM大模型,过程中不需要人工介入,会不断自动调整,所以比较依赖于大模型本身的响应速度。

墨痕诉清风的博客 2415

基于socket实现一个简易的web服务器

在浏览器输入一个段网址,就会出现你想要的网页或数据,那么这个过程是如何做到的,今天我们来探索这个过程。

秋雨 De Blog 1822

ardupilot 获取避障传感器数据

本节主要记录自己学ardupilot的避障代码过程,首先看下怎么注册避障传感器,然后怎么获取避障数据。

陌城烟雨 5525

python搭建web服务器_Python实现简易版的Web服务器(推荐)

下面给大家介绍python实现简易版的web服务器,具体内容详情大家通过本文学习吧!1、请自行了解HTTP协议//www.jb51.net/article/133883.htm(点击跳转)2、创建Socket服务,监听指定IP和端口3、以阻塞方式等待客户端连接4、读取客户端请求数据并进行解析5、准备服务器运行上下文6、处理客户端请求数据7、根据用户请求路径读取文件8、返回响应结果给客户端9、程序入...

weixin_39835117的博客 501

AI 赋能 DBA:如何用 DeepSeek 等大模型简化数据库管理工作

数据库管理员(DBA)的工作涉及 SQL 优化、故障排查、性能监控等复杂任务。而 DeepSeek、ChatGPT 等大模型可以大幅减少重复劳动,提高 DBA 的工作效率。:某电商平台的订单查询 SQL 执行缓慢(平均 5s),DBA 需要优化。:DBA 需要监控 MySQL 的慢查询,但不想手动写脚本。:某金融系统频繁出现数据库死锁,DBA 需要快速定位原因。:大模型可分析 SQL 执行计划,推荐索引优化方案。:用日常语言查询数据库,无需写复杂 SQL。:一键生成监控、备份、数据迁移脚本。

专注于编程、AI、人工智能等多个领域内容研究和分析 565

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Python简易web静态服务器程序搭建

http.server模块介绍,Python多任务与TCP实现简易静态web服务器程序

ncu5509121083的博客 3740

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

python实现web服务器介绍_基于python实现简易web服务器

摘要本文主要是介绍用python自带的BaseHTTPRequestHandler,HTTPServer类实现一个简易的web服务器,从而加深对http协议和web服务器实现、运行原理的理解,同时对web服务器与客户端的交互过程进行详细介绍,明白服务器是如何处理客户端对其请求后,将服务器资源响应给客户端的,更重要的是通过本项目的实现可以了解python的网络开发基础模块和CGI协议,从而拥有能够深...

weixin_32009809的博客 925

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

Python小姿势 - 如何用Python实现一个简单的Web服务器

《100天精通Python》专栏,最详细的Python入门到精通教程;理论篇基础知识讲解构建体系框架、实践篇Python黑科技学以致用。https://blog.youkuaiyun.com/weixin_39032019/category_12263598.html

独木不成林,单弦不成音。 3517

使用Python打造简易Web服务器

在开发过程中,了解Web服务器的工作原理是非常有用的。Python作为一个功能强大的编程语言,提供了模块,让我们能够快速地搭建一个简易的Web服务器。本文将指导你如何使用Python的模块来创建一个简单的Web服务器,并附上完整的代码示例。

Xiaoxin的博客 718

python web server_Python实现简易版的Web服务器(推荐)

下面给大家介绍python实现简易版的web服务器,具体内容详情大家通过本文学习吧!1、请自行了解HTTP协议2、创建Socket服务,监听指定IP和端口3、以阻塞方式等待客户端连接4、读取客户端请求数据并进行解析5、准备服务器运行上下文6、处理客户端请求数据7、根据用户请求路径读取文件8、返回响应结果给客户端9、程序入口10、目录结构11、运行python wsgiserver.py app:r...

weixin_39959569的博客 453
上一篇: 利用 Python 引导孩子的计算机思维!
下一篇: 用Python执行SQL、Excel常见任务?10个方法全搞定!
普通网友
博客等级 码龄7年 438粉丝 263原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值