Python SimpleHTTPServer.py 源碼分析

前面分析BaseServer和BaseHTTPServer咧最,可以知道BaseHTTPRequestHandler中handle_one_request方法將會(huì)通過自省的方式制轰,調(diào)用HTTP客戶端請求的方法余蟹。

def handle_one_request(self):
    try:
        # 省略... ...

        method = getattr(self, mname)
        method()
        self.wfile.flush() #actually send the response if not already done.
    except socket.timeout, e:
        # 省略... ...

顯然method將會(huì)在BaseHTTPRequestHandler的子類中實(shí)現(xiàn)迎瞧。我們已經(jīng)知道哮缺,但凡以Base開頭的class苟穆,都需要被繼承,然后在其子類中實(shí)現(xiàn)相關(guān)的方法稿黄。

SimpleHTTPServer

構(gòu)建一個(gè)簡單的HTTP服務(wù)喊衫,需要繼承HTTPServer,同時(shí)requesthandler也需要繼承BaseHTTPRequestHandler杆怕。python已經(jīng)實(shí)現(xiàn)了一個(gè)例子格侯,那就是SimpleHTTPServer。因此分析SimpleHTTPServer來查看如何使用前面的一些類構(gòu)建http服務(wù)财著。

曾經(jīng)為了表示python的簡潔優(yōu)雅联四,經(jīng)常會(huì)舉這樣的例子,python可以一行代碼開啟一個(gè)服務(wù)器撑教。

$ python -m SimpleHTTPServer

這里的SimpleHTTPServer就是實(shí)現(xiàn)了HTTPServer的模塊正林。

SimpleHTTPServer通過調(diào)用BaseHTTPServer模塊的test方法做為入口驶忌。

def test(HandlerClass = SimpleHTTPRequestHandler,
         ServerClass = BaseHTTPServer.HTTPServer):
    BaseHTTPServer.test(HandlerClass, ServerClass)

test方法做了兩件事售滤,第一件就是使用HTTPServer接受一個(gè)監(jiān)聽地址和requestClass參數(shù),創(chuàng)建了一個(gè)實(shí)例對象亿卤,調(diào)用server_forever方法開啟服務(wù)。

SimpleHTTPRequestHandler

根據(jù)之前的分析鹿霸,使用httpserver的服務(wù)排吴,我們只需要繼續(xù)BaseHTTPRequestHandler,并提供自省的method方法即可懦鼠。

class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    server_version = "SimpleHTTP/" + __version__

    def do_GET(self):
        f = self.send_head()
        if f:
            self.copyfile(f, self.wfile)
            f.close()

    def do_HEAD(self):
        f = self.send_head()
        if f:
            f.close()

do_GET 和 do_HEAD 分別實(shí)現(xiàn)了http的get請求和head請求的處理钻哩。他們調(diào)用send_head方法:

    def send_head(self):

        path = self.translate_path(self.path)
        f = None
        if os.path.isdir(path):
            if not self.path.endswith('/'):
                self.send_response(301)
                self.send_header("Location", self.path + "/")
                self.end_headers()
                return None
            for index in "index.html", "index.htm":
                index = os.path.join(path, index)
                if os.path.exists(index):
                    path = index
                    break
            else:
                return self.list_directory(path)
        ctype = self.guess_type(path)
        try:
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        self.send_response(200)
        self.send_header("Content-type", ctype)
        fs = os.fstat(f.fileno())
        self.send_header("Content-Length", str(fs[6]))
        self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
        self.end_headers()
        return f

send_head 方法通過uri的path分析得到客戶請求的網(wǎng)路路徑。構(gòu)造head的mime元信息并發(fā)送到客戶端肛冶,然后返回一個(gè)打開path的文件句柄街氢。

copyfile

do_GET的下一步就是通過 copyfile方法,將客戶請求的path的文件數(shù)據(jù)寫入到緩沖可寫文件中睦袖,發(fā)送給客戶端珊肃。

list_directory

SimpleHTTPServer模塊還提供了list_directory方法,用于響應(yīng)path是一個(gè)目錄馅笙,而不是文件的情況伦乔。

def list_directory(self, path):
    try:
        list = os.listdir(path)
    except os.error:
        self.send_error(404, "No permission to list directory")
        return None
    list.sort(key=lambda a: a.lower())
    f = StringIO()
    displaypath = cgi.escape(urllib.unquote(self.path))
    f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
    f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
    f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
    f.write("<hr>\n<ul>\n")
    for name in list:
        fullname = os.path.join(path, name)
        displayname = linkname = name
        # Append / for directories or @ for symbolic links
        if os.path.isdir(fullname):
            displayname = name + "/"
            linkname = name + "/"
        if os.path.islink(fullname):
            displayname = name + "@"
            # Note: a link to a directory displays with @ and links with /
        f.write('<li><a href="%s">%s</a>\n'
                % (urllib.quote(linkname), cgi.escape(displayname)))
    f.write("</ul>\n<hr>\n</body>\n</html>\n")
    length = f.tell()
    f.seek(0)
    self.send_response(200)
    encoding = sys.getfilesystemencoding()
    self.send_header("Content-type", "text/html; charset=%s" % encoding)
    self.send_header("Content-Length", str(length))
    self.end_headers()
    return f

由此可見,處理客戶端的請求董习,只需要使用 send_reponse评矩, send_header 和 end_headers ,就能向客戶端發(fā)送reponse阱飘。

自定義http服務(wù)

定義一個(gè)CustomHTTPRequestHadnler繼承自BaseHTTPRequestHandler。在其內(nèi)實(shí)現(xiàn)do_GET 方法來處理get請求虱颗。

然后再定義一個(gè)CustomHTTPServer繼承自HTTPServer沥匈,它接受CustomHTTPRequestHadnler作為自己的handler。簡單的代碼如下:

# -*- coding: utf-8 -*-

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer


class CustomHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.wfile.write("hello world\r\n")


class CustomHTTPServer(HTTPServer):
    def __init__(self, host, port):
        HTTPServer.__init__(self, (host, port), CustomHTTPRequestHandler)


def main():
    server = CustomHTTPServer('127.0.0.1', 8000)
    server.serve_forever()


if __name__ == '__main__':
    main()

使用curl訪問可以得到

?  ~  curl http://127.0.0.1:8000
hello world
?  ~

控制臺(tái)會(huì)打出訪問的log忘渔。

127.0.0.1 - - [01/Jun/2015 11:42:33] "GET / HTTP/1.1" 200 -

從socket的建立高帖,select的IO模式,再到Server和Handler的組合構(gòu)建服務(wù)畦粮。我們已經(jīng)熟悉了python的基本網(wǎng)絡(luò)編程散址。python的web開發(fā)中,更多是使用WSGI協(xié)議宣赔。實(shí)現(xiàn)該協(xié)議的還有 uWSGI和gunicorn等庫预麸。相比那些庫,python內(nèi)部提供了一個(gè)wsgiref模塊儒将,實(shí)現(xiàn)了一個(gè)簡單wsgi服務(wù)--simple_server吏祸。

接下來將會(huì)通過分析simple_server,更好的掌握WSGI協(xié)議钩蚊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贡翘,一起剝皮案震驚了整個(gè)濱河市蹈矮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鸣驱,老刑警劉巖泛鸟,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異踊东,居然都是意外死亡北滥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門递胧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碑韵,“玉大人,你說我怎么就攤上這事缎脾∽N牛” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵遗菠,是天一觀的道長联喘。 經(jīng)常有香客問我,道長辙纬,這世上最難降的妖魔是什么豁遭? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮贺拣,結(jié)果婚禮上蓖谢,老公的妹妹穿的比我還像新娘。我一直安慰自己譬涡,他們只是感情好闪幽,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涡匀,像睡著了一般盯腌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上陨瘩,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天腕够,我揣著相機(jī)與錄音,去河邊找鬼舌劳。 笑死帚湘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的甚淡。 我是一名探鬼主播客们,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了底挫?” 一聲冷哼從身側(cè)響起恒傻,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎建邓,沒想到半個(gè)月后盈厘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡官边,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年沸手,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片注簿。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡契吉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诡渴,到底是詐尸還是另有隱情捐晶,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布妄辩,位于F島的核電站惑灵,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏眼耀。R本人自食惡果不足惜英支,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望哮伟。 院中可真熱鬧干花,春花似錦、人聲如沸楞黄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谅辣。三九已至,卻和暖如春婶恼,著一層夾襖步出監(jiān)牢的瞬間桑阶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來泰國打工勾邦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蚣录,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓眷篇,卻偏偏與公主長得像萎河,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容