基於Lua和C實現的HTTP服務器

前言

首先須要說明的是作這個小項目純粹出於學習目的,目前尚未多大的實用價值。只是以爲使用Lua和C結合來實現一個HTTP服務器的這種架構很是簡潔和易用,值得專門造一個輪子來深刻了解和學習這種用法,順便也實際動手體驗一下實現一個HTTP服務器的感受,固然裏面還有不少不完整或者說沒有考慮周全的地方:好比接收HTTP請求的時候默認了HTTP請求頭的全部數據都在一個數據包中一次性讀取完成,這在生產環境中確定不行(這個問題留着之後解決吧)。html

首先介紹一下這個HTTP服務器實現的功能:linux

  1. 能夠在Lua腳本中註冊URL,瀏覽器訪問這個URL時能夠調用執行Lua中註冊的鉤子函數,實現HTTP服務器的業務邏輯使用Lua腳本語言來處理。
  2. 使用epoll系統調用,實現數據的接收和發送都是單進程異步的方式。
  3. Log日誌系統,提供一個分級的日誌接口,實現將全部日誌都存儲在一個日誌文件中。

實現這麼一個HTTP服務器能夠學習到的東西:數組

  1. Lua和C兩種語言之間的交互過程,以及如何使用C來編寫Lua的函數庫。
  2. epoll系統調用實現數據的異步接收和發送
  3. Log日誌的集中處理
  4. 一個HTTP服務器的完整處理流程:接受到請求、解析請求、請求處理、構造請求回覆頭、發送請求文件給瀏覽器。

Lua註冊鉤子函數的實現

lua/task_test.lua文件中給出了Lua中註冊鉤子函數的示例程序,首先要加載libtask這個庫,而後調用task.regExecutor("HTTPGET:/lua_hello.html", 0, callback)來註冊鉤子函數,他的意思是假如你使用GET請求訪問這個連接http://server_ip/lua_hello.html時會調用到Lua中的callback函數。其中第二個參數0表示這個鉤子函數的優先級,也就是說同一個URI能夠註冊多個鉤子函數來處理,而後服務器會按照這個優先級來依次調用註冊的鉤子函數。瀏覽器

註冊鉤子函數的功能是在libtask.c這個文件中實現的,它的實質就是以URI爲哈希的Key,一個任務的結構體指針爲Value存儲在哈希表中(一個任務結構體就是一個鉤子函數執行時所須要的全部元素的集合)。由於須要支持一個URI能夠註冊多個鉤子函數因此每一個哈希槽中存儲的是一個鏈表頭,而後這個鏈表上按照優先級順序掛着這個URI註冊的全部任務。當HTTP請求到來時,以URI來查詢哈希表找到對應的鏈表,而後遍歷執行鏈表上的全部任務。如圖: 
hash 
圖1. 原諒個人畫圖水平服務器

其中HTTP請求時的全部參數也所有解析爲鍵值對,存放在一個哈希表中,Lua中經過local param = task:getParam()接口能夠獲取到參數的引用,而後調用param:get("User-Agent")獲取到對於的值,其中lua/param_test.lua文件中給出了使用示例。架構

Lua鉤子函數處理請求以後的返回數據經過調用task:replay(replay)其中replay參數組織爲一個table表,傳遞到C中而後合併爲一個返回請求,其中lua/return_test.lua文件中給出了使用示例。異步

epoll

網上關於epoll、select分析的文章處處都是,在這裏就只記錄一些比較重要的地方: 
關於select調用的幾個缺點:函數

  1. select監聽的句柄有最大數量的限制,在Linux上的限制是1024。除非修改代碼從新編譯內核否則不能改變這個限制。
  2. 每次調用select時都要將監聽的全部句柄所有下發到內核,select返回時也是將全部句柄所有上傳給用戶空間,這種來回複製很是消耗性能。
  3. 每次調用select時,在內核中都須要遍歷全部句柄,將它們挨個放到等待隊列中。

而epoll克服了上述全部缺陷,他沒有最大數量的限制,它是經過一個專門的函數接口來增長或者刪除你須要監聽的句柄,因此沒必要每次都所有下發一邊,而且它接收到事件返回的時候也只是將當前就緒的句柄返回給用戶空間而不是全部句柄。其中epoll中須要關注的一點是它的兩種模式:LT模式與ET模式,它們之間的區別是:性能

  • LT模式,當epoll_wait檢測到描述符事件發生並將此事件通知應用程序,應用程序能夠不當即處理該事件。下次調用epoll_wait時,會再次響應應用程序並通知此事件。
  • ET模式,當epoll_wait檢測到描述符事件發生並將此事件通知應用程序,應用程序必須當即處理該事件。若是不處理,下次調用epoll_wait時,不會再次響應應用程序並通知此事件。

這兩種區別在內核中的實現參考這篇文章:Linux內核epoll ET/LT辨析 
select在內核中的實現參考這篇文章:Linux內核select源碼剖析 
epoll、select之間的區別參考這篇文章:select、poll、epoll之間的區別總結[整理]學習

HTTP的處理

其中HTTP頭的解析是本身寫的一個很是簡陋的解析器,其中將URL中攜帶的參數和請求頭中的全部參數解析成key-value的形式,所有放在了一個哈希表中傳遞到Lua中,在Lua中能夠經過key來獲取參數值。Lua回調函數處理完成後的返回數據是經過一個table表傳遞到C語言中,而後經過這個表構造HTTP的返回頭部,返回給瀏覽器。固然這個HTTP服務器也能夠返回文件給瀏覽器,返回文件所有在C中作的,經過Linux的一個sendfile系統調用實現異步發送數據到客戶端。

編譯

首先須要下載Lua5.2.4的源碼編譯安裝,另外我將Lua編譯爲一個動態庫放在了源碼目錄,程序執行時加載動態庫和其餘Lua庫同樣。我已經將所需的庫文件放在了源碼目錄,下載後直接make就行。

相關文章
相關標籤/搜索