ホームページ  >  記事  >  バックエンド開発  >  Pythonに付属するhttpモジュールの詳しい説明

Pythonに付属するhttpモジュールの詳しい説明

高洛峰
高洛峰オリジナル
2017-02-22 10:45:143996ブラウズ

私は長い間ブログを書いていませんでした。なぜなら、ブロガーは今年、プロジェクトを学び、取り組むという素晴らしいインターンシップの経験を始めたからです。そして、私は今年、この 2 つの経験にとても感謝しています。 golang と python に触れて、さまざまなことを学ぶことで、C/C++ を学ぶというこれまでの考え方の限界を打ち破り、golang と Python の優れた機能を学び、言語の違いを理解することができます。さまざまなシナリオに適しています。また、以前に Linux と C/C++ を学習したことで、すぐに golang と Python を使い始めることができました。

私の学習習慣は、使い方を学ぶことに加えて、つまり、これらの言語やフレームワークを使用することは、食べることと寝ることと同じで、ごく自然なことです。 flask Web フレームワーク、これら 2 つのフレームワークのソース コードを見てみたいと思いますが、これら 2 つのフレームワークは Python に付属の http に基づいているため、この記事はここにあります

python http simple example

Python http フレームワークは主にサーバーとハンドラーで構成されます。サーバーは、ソケットを監視するために epoll を使用するなど、主にネットワーク モデルを構築するために使用されます。まず、Python の簡単な使用法を見てみましょう。 http:

import sys
from http.server import HTTPServer,SimpleHTTPRequestHandler

ServerClass = HTTPServer
HandlerClass = SimpleHTTPRequestHandler

if__name__ =='__main__':
 port = int(sys.argv[2])
 server_address = (sys.argv[1],port)
 httpd = ServerClass(server_address,HandlerClass)

sa=httpd.socket.getsockname()
print("Serving HTTP on",sa[0],"port",sa[1],"...")

try:
 httpd.serve_forever()
 except KeyboardInterrupt:
print("\nKeyboard interrupt received, exiting.")
 httpd.server_close()
 sys.exit(0)

上記の例を実行すると、次の結果が得られます:

python3 myhttp.py 127.0.0.1 9999

この時点で、現在のフォルダーに新しいindex.htmlファイルを作成すると、次の方法でアクセスできます。 http://127.0.0.1:9999/index.htmlindex.html ページ。

この例のサーバー クラスは HTTPServer を使用し、ハンドラー クラスは SimpleHTTPRequestHandler です。したがって、HTTPServer はリクエストの到着を監視するときに、処理のためにリクエストを SimpleHTTPRequestHandler クラスにスローします。これらを理解した後、分析を開始します。

httpサーバー

httpモジュールの設計は、以前にtfsファイルシステムのコードを読んだことがあったので、そうは感じませんでした。 Python http を見るととてもプレッシャーになります ; まずサーバーの継承関係を与えます

 +------------------+
+------------+| tcpserver基类 |
| BaseServer +-------->| 开启事件循环监听 |
+-----+------+ | 处理客户端请求 |
 | +------------------+
 v +-----------------+
+------------+| httpserver基类 |
| TCPServer +-------->+设置监听socket |
+-----+------+ | 开启监听 |
 | +-----------------+
 v
+------------+
| HTTPServer | 
+------------+

継承関係は上の図に示されているとおりです。BaseServer と TCPServer はファイル sockserver.py にあります。 http/server.py; まず BaseServer を見てみましょう

BaseServer

BaseServer はすべてのサーバーの基本クラスであるため、BaseServer はすべてのサーバーの共通機能を可能な限り抽象化します。イベント リスニング ループ。これはすべてのサーバーに共通の機能なので、BaseServer が主に行うことでもあります。BaseServer のメイン コード部分

defserve_forever(self, poll_interval=0.5):
 self.__is_shut_down.clear()
try:
with_ServerSelector()asselector:
 selector.register(self, selectors.EVENT_READ)

whilenotself.__shutdown_request:
 ready = selector.select(poll_interval)
ifready:
 self._handle_request_noblock()

 self.service_actions()
finally:
 self.__shutdown_request = False
 self.__is_shut_down.set()

コード内のセレクターは、実際には、 select、poll、epoll などをカプセル化して、サービス自体によって監視されているソケットを IO 多重化に登録し、イベント監視をオンにします。クライアントが接続すると、次にリクエストを処理するために self._handle_request_noblock() が呼び出されます。この処理関数が何をするのか見てみましょう。

def_handle_request_noblock(self):
try:
 request, client_address = self.get_request()
exceptOSError:
return
ifself.verify_request(request, client_address):
try:
 self.process_request(request, client_address)
except:
 self.handle_error(request, client_address)
 self.shutdown_request(request)
else:
 self.shutdown_request(request)

_handle_request_noblock 関数は内部関数であり、まずクライアント側の接続要求を受信し、最下層が実際にシステム コール受け入れ関数をカプセル化し、次に要求を検証します。そして最後に process_request を呼び出してリクエストを処理します。ここで get_request はサブクラスに属するメソッドです。これは、tcp と udp ではクライアント リクエストの受け取り方が異なるためです (tcp には接続があり、udp には接続がありません)。 process_request が具体的に何を行うかを見てみましょう。

defprocess_request(self, request, client_address):
 self.finish_request(request, client_address)
 self.shutdown_request(request)
# -------------------------------------------------
deffinish_request(self, request, client_address):
 self.RequestHandlerClass(request, client_address, self)

defshutdown_request(self, request):
 self.close_request(request)

process_request 関数は、最初にfinish_request を呼び出して接続を処理します。処理が完了した後、shutdown_request 関数が呼び出され、接続を閉じます。そして、finish_request 関数は内部でハンドラー クラスをインスタンス化し、クライアントのソケットとこれは、初期化が完了したときにハンドラー クラスがリクエストの処理を完了することを示しています。これについては、後でハンドラーを分析するときに詳しく説明します。この BaseServer は直接使用できません。いくつかの関数はまだ実装されておらず、tcp/udp の抽象化レイヤーとしてのみ使用されているためです。 要約すると、

まず、serve_forever を呼び出してイベント監視を有効にします。

次に、クライアント リクエストが届いたら、そのリクエストを処理のためにハンドラーに渡します。 ;


TCPServer

上記の BaseServer によって抽象化された関数から、TCPServer または UDPServer が完了する必要がある関数は、リスニング ソケットの初期化、リスニングのバインド、そして最後に、クライアントリクエスト、このクライアントを受信します。コードを見てみましょう

BaseServer==>
def__init__(self, server_address, RequestHandlerClass):
"""Constructor. May be extended, do not override."""
 self.server_address = server_address
 self.RequestHandlerClass = RequestHandlerClass
 self.__is_shut_down = threading.Event()
 self.__shutdown_request = False
#--------------------------------------------------------------------------------
TCPServer==>
def__init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
 BaseServer.__init__(self, server_address, RequestHandlerClass)
 self.socket = socket.socket(self.address_family,
 self.socket_type)
ifbind_and_activate:
try:
 self.server_bind()
 self.server_activate()
except:
 self.server_close()
raise

TCPサーバーが初期化されると、まず基本クラスBaseServerの初期化関数が呼び出され、サーバーアドレス、ハンドラークラスなどが初期化されます。次に、独自の listen ソケットを初期化し、最後に、server_bind を呼び出してソケットをバインドし、server_activate を呼び出してソケットをリッスンします

defserver_bind(self):
ifself.allow_reuse_address:
 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 self.socket.bind(self.server_address)
 self.server_address = self.socket.getsockname()

defserver_activate(self):
 self.socket.listen(self.request_queue_size)

TCPServer は、クライアント要求を受信する別の関数

defget_request(self):
returnself.socket.accept()

も実装します

プログラミングを始める前に Linux を学んだことがある方は、これらのコードを見ると親しみを感じるはずです。関数名は Linux が提供するシステム コール名とまったく同じであるため、ここでは詳しく説明しません。

TCPServer には、実際には tcp に基づいてメインのサーバー フレームワークを構築したため、HTTPServer は TCPServer に基づいて継承し、server_bind 関数をオーバーロードし、reuse_address などを設定するだけです

;

ok,这里分析下上述例子程序的开启过程;

httpd = ServerClass(server_address,HandlerClass)这行代码在初始化HTTPServer时,主要是调用基类TCPServer的初始化方法,初始化了监听的套接字,并绑定和监听;
httpd.serve_forever()这行代码调用的是基类BaseServer的serve_forever方法,开启监听循环,等待客户端的连接;
如果有看过redis或者一些后台组件的源码,对这种并发模型应该很熟悉;ok,分析了server之后,接下来看下handler是如何处理客户端请求的。

http之handler

handler类主要分析tcp层的handler和http应用层的handler,tcp层的handler是不能使用的,因为tcp层只负责传输字节,但是并不知对于接收到的字节要如何解析,如何处理等;因此应用层协议如该要使用TCP协议,必须继承TCP handler,然后实现handle函数即可;例如,http层的handler实现handle函数,解析http协议,处理业务请求以及结果返回给客户端;先来看下tcp层的handler

tcp层handler

tcp层handler主要有BaseRequestHandler和StreamRequestHandler(都在socketserver.py文件),先看下BaseRequestHandler代码,

classBaseRequestHandler:
def__init__(self, request, client_address, server):
 self.request = request
 self.client_address = client_address
 self.server = server
 self.setup()
try:
 self.handle()
finally:
 self.finish()

defsetup(self):
pass

defhandle(self):
pass

deffinish(self):
pass

之前在看server时,知道处理客户端请求就是在handler类的初始化函数中完成;由这个基类初始化函数,我们知道处理请求大概经历三个过程:

  1. setup对客户端的socket做一些设置;

  2. handle真正处理请求的函数;

  3. finish关闭socket读写请求;

这个BaseRequestHandler是handler top level 基类,只是抽象出handler整体框架,并没有实际的处理;我们看下tcp handler,

classStreamRequestHandler(BaseRequestHandler):
 timeout = None
 disable_nagle_algorithm = False

defsetup(self):
 self.connection = self.request
ifself.timeoutisnotNone:
 self.connection.settimeout(self.timeout)
ifself.disable_nagle_algorithm:
 self.connection.setsockopt(socket.IPPROTO_TCP,
 socket.TCP_NODELAY, True)
 self.rfile = self.connection.makefile('rb', self.rbufsize)
 self.wfile = self.connection.makefile('wb', self.wbufsize)

deffinish(self):
ifnotself.wfile.closed:
try:
 self.wfile.flush()
exceptsocket.error:
pass
 self.wfile.close()
 self.rfile.close()

tcp handler实现了setup和finish函数,setup函数设置超时时间,开启nagle算法以及设置socket读写缓存;finish函数关闭socket读写;

由上述两个tcp层的handler可知,要实现一个基于http的服务器handler,只需要继承StreamRequestHandler类,并实现handle函数即可;因此这也是http层handler主要做的事;

http层handler

由之前tcp层handler的介绍,我们知道http层handler在继承tcp层handler基础上,主要是实现了handle函数处理客户端的请求;还是直接看代码吧;

defhandle(self):
 self.close_connection = True

 self.handle_one_request()
whilenotself.close_connection:
 self.handle_one_request()

这就是BaseHTTPRequestHandler的handle函数,在handle函数会调用handle_one_request函数处理一次请求;默认情况下是短链接,因此在执行了一次请求之后,就不会进入while循环在同一个连接上处理下一个请求,但是在handle_one_request函数内部会进行判断,如果请求头中的connection为keep_alive或者http版本大于等于1.1,则可以保持长链接;接下来看下handle_one_request函数是如何处理;

defhandle_one_request(self):
try:
self.raw_requestline =self.rfile.readline(65537)
iflen(self.raw_requestline) >65536:
self.requestline =''
self.request_version =''
self.command =''
self.send_error(HTTPStatus.REQUEST_URI_TOO_LONG)
return
ifnotself.raw_requestline:
self.close_connection = True
return
ifnotself.parse_request():
return
 mname = 'do_'+self.command
ifnothasattr(self, mname):
self.send_error(
 HTTPStatus.NOT_IMPLEMENTED,
"Unsupported method (%r)"%self.command)
return
 method = getattr(self, mname)
 method()
self.wfile.flush()
 except socket.timeout as e:
self.log_error("Request timed out: %r", e)
self.close_connection = True
return

这个handle_one_request执行过程如下:

  1. 先是调用parse_request解析客户端http请求内容

  2. 通过"do_"+command构造出请求所对于的函数method

  3. 调用method函数,处理业务并将response返回给客户端

这个BaseHTTPRequestHandler是http handler基类,因此也是无法直接使用,因为它没有定义请求处理函数,即method函数;好在python为我们提供了一个简单的SimpleHTTPRequestHandler,该类继承了BaseHTTPRequestHandler,并实现了请求函数;我们看下get函数:

# SimpleHTTPRequestHandler
# ---------------------------------------------
defdo_GET(self):
"""Serve a GET request."""
 f = self.send_head()
iff:
try:
 self.copyfile(f, self.wfile)
finally:
 f.close()

这个get函数先是调用do_GET函数给客户端返回response头部,并返回请求的文件,最后调用copyfile函数将请求文件通过连接返回给客户端;

以上就是http模块最基础的内容,最后,总结下例子程序handler部分:

  1. server把请求传给SimpleHTTPRequestHandler初始化函数;

  2. SimpleHTTPRequestHandler在初始化部分,对这个客户端connection进行一些设置;

  3. 接着调用handle函数处理请求;

  4. 在handle函数接着调用handle_one_request处理请求;

  5. 在handle_one_request函数内部,解析请求,找到请求处理函数;

  6. 我之前的访问属于get访问,因此直接调用do_GET函数将index.html文件返回给客户端;

Python の http モジュールの分析は終了しました。Python に付属の http モジュールは、request メソッドを通じて request 関数を呼び出すため、あまり使いにくいことに気づいたでしょうか。 get メソッドや post メソッドなどの呼び出しが非常に頻繁に行われると、リクエスト関数が非常に大きくなり、さまざまな状況でコードを作成したり判断したりするのが難しくなります。もちろん、SimpleHTTPRequestHandler は Python によって提供される単純な例にすぎません。

もちろん、Python は http フレームワーク用に、より便利な wsgi サーバーと wsgi アプリケーションを公式に提供しています。次の記事では、まず Python に付属の wsgiref モジュールとボトルを分析し、次に flask を分析します。 Python に付属の http モジュールについては、PHP の中国語 Web サイトに注意してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。