A Simple Web Server

Posted 一张红枫叶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了A Simple Web Server相关的知识,希望对你有一定的参考价值。

介绍

在过去20几年里,网络已经在各个方面改变了我们的生活,但是它的核心却几乎没有什么改变。多数的系统依然遵循着Tim Berners-Lee在上个世纪发布的规则。大多数的web服务器都在用同样的方式处理消息


背景

多数在web上的服务器都是运行在IP协议标准上。在这协议家族里面我们关心的成员就是TCP,这个协议使得计算机之间的通信看起来像是在读写文件。


项目通过套接字来使用IP通信。每个套接字都是一个点对点的通信信道,一个套接字包含IP地址,端口来标识具体的机器。IP地址包含48Bit的数字,比如174.136.14.108DNS将这些数字匹配到更加容易识别的名字比如aosabook.org,这样更加便于人们记住。


HTTP是一种可以在IP之上传输数据的方式。HTTP非常简单:客户端在套接字连接上发送一个请求指示需要什么样的信息,然后服务端就发送响应。数据可以是从硬盘上的文件拷贝过来,程序动态生成,或者是两者结合

HTTP请求中最重要的就是文本:任何项目都可以创造或者解析一个文本。为了便于理解,文本有图中所示的部分

HTTP方法一般采用”GET”(去获取信息)或者”POST”(去提交表单数据或者上传文件)URL指明了客户端想要的;一般是硬件上文件的路径,比如/research/experiments.html,但是这一切都取决于服务器端如何去做。HTTP版本一般是"HTTP/1.0"或者"HTTP/1.1";我们并不关心这两者的差别。


HTTP的头是像下面的成对键值:

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

和哈希表中的键值不一样的是,键值在HTTP头中可以出现任意的次数。这就使得请求可以去指定它愿意接受的几种类型。


最后,请求的主体是与请求相关联的任何额外数据。这些将被用在通过表单提交数据,上传文件等等。在最后一个标头和主体的开始之间必须有空白行以表示标头的结束。


一个被称为Content-length的头,用来告诉在请求数据中期望读取多数个字节。


HTTP响应也和HTTP请求是一样的格式

 


 

版本,头信息和主体都是同样的格式。状态码是一个数字用来指示请求处理时发生了什么:200意味着正常工作,404意味着没有找到,其他的码也有不同的意思。

 


 

对于这章节,我们只需要知道HTTP的其他两件事。

 


 

第一个就是无状态:每个请求都处理自己的,并且服务器端。服务器不会记住当前请求和下一个请求之间的内容。如果应用想跟踪比如用户身份的信息,就必须自己处理。

 


 

通常采用的方法是用cookiecookie是服务器发送给客户端的字符流,然后客户端返回给服务器。当一个用户需要实现在不同请求之间保持状态的时候,服务器会创建cookie,存储在数据库里,然后发送给浏览器。每次浏览器把cookie值发送回来的时候,服务器都会用来去查找信息来知道用户在干什么。

 


 

第二个我们需要了解关于HTTP的就是URL可以通过提供参数来提供更多的信息。比如,如果我们在使用搜索引擎,我们必须指定搜索术语。我们可以加入到URL的路径中,但是我们一般都是加入到URL的参数中。我们在URL中增加?,后面跟随key=value并且用&符号分割来达到这个目的。比如URLhttp://www.google.ca?q=Python就告诉Google去搜索Python相关的网页。键值是字母q,值是Python。更长的查询http://www.google.ca/search?q=Python&client=Firefox告诉Google我们正在使用Firefox等等。我们可以传输任何我们需要的参数。但是使用哪一个,如何解释这些参数取决于应用。

 


 

当然,如果?&特殊的字符,那么必须有一种方法去规避,正如必须有一种方法将双引号字符放入由双引号分隔的字符串中一样URL的编码标准用%后面跟2个字节码的方式来表示特殊字符,用+来代替空格。所以为了在Google上搜索”grade=A+”,我们可以使用的URLhttp://www.google.ca/search?q=grade+%3D+A%2B

 

创建sockets,构建HTTP请求,解析响应是非常枯燥的事情。所以人们更多是使用库函数来完成大部分的工作。Python附带了一个urllib2的库,但是它暴露了很多人根本不关心的管道。Request库是可以替代urllib2并且更加好使用的库。下面是一个从AOA网站下载网页的例子。

 

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是真是的数据(在这个例子中,是HTTP网页)

 


 

你好,web

 

现在我们准备去写第一个简单的web服务器。

 

1 等待某人连接到服务器上并且发送一个请求

 

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重写了这个方法来动态生成网页:文本text存储在类级别的参数page,Page将会在发送了200响应码后发送给客户端,Content-Type头告诉客户端用HTML的方式来解析数据以及网页的长度(end_headers方法在我们的头和网页之间插入空白行) 但是RequestHandler并不是整个的工程:我们依然需要最后的三行启动服务器。第一行用一个元组的方式来定义服务器的地址:空字符意味着运行在本机上,8080是端口。然后我们用整个地址和RequestHandler作为参数来创建BaseHTTPServer.HTTPServer实例,然后让程序永远运行(在实际中,除非用Control-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)

想要显示的网页模板是一个字符串,其中包含了HTML表格以及一些格式化的占位符 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:8080/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 即使something.html网页不在网页上,我们也没有发现404异常。这是因为服务器只是一个程序,当收到请求时,它可以做任何它想做的事:发送回前一个请求中命名的文件,提供随机选择的维基百科页面,或者我们对它进行编程的任何其他内容。 静态网页 下一步就是从硬盘上的网页开始启动而不是随机产生一个。我们可以重写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) 这个函数假设被允许web服务器正在运行的目录或者目录下的任何文件(通过os.getcwd来获取)。程序会将URL中包含的路径和当前的路径组装起来(URL中的路径放在self.path变量中,初始化的时候都是’/’)来得到用户需要的文件路径 如果路径不存在,或者不是个文件,函数将会通过产生并捕获一个异常来报告错误。如果路径和文件匹配,则会调用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的状态码,即使被请求的的网页不存在。是的,在这种情况下,发送回的页面包含错误信息,但是浏览器不能阅读英文,所以也不知道request是成功还是失败。为了让这种情况更清晰,我们需要修改handle_errorsend_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是为了在我们自己搞错的时候发送一个内部错误的信号。handle_error创建的异常网页,只会在用户发生错误的时候发生。比如发送URL中的文件并不存在。 显示目录 下一步,我们将教会服务器当URL是一个目录而不是文件的时候显示路径的内容。我们还可以走远一点在路径中去寻找index.html文件并显示出来,并且在文件不存在的时候显示路径的内容。 但是在do_GET中建立这些规则将会是个错误,因为所得到的方法将是一长串控制特殊行为的if语句。正确的解决方法是退后并解决一般性问题,那就是指出URL将要发生的动作。下面是对do_GET的重写。 def do_GET(self): try: # Figure out what exactly is being requested. self.full_path = os.getcwd() + self.path # Figure ou

以上是关于A Simple Web Server的主要内容,如果未能解决你的问题,请参考以下文章

Esp8266 Web Server 应用实例 - Simple Web Console

A simple in-process HTTP server for UWP

django的web server的源代码流程

csharp C#代码片段 - 使类成为Singleton模式。 (C#4.0+)https://heiswayi.github.io/2016/simple-singleton-pattern-us

Python web服务器

srs(simple Rtmp Server )的一些说明