在開始打造本身的服務器以前,咱們首先明確一下服務器的定義:一個管理資源併爲用戶提供服務的計算機軟件。css
- static web server,例如常見的nginx apache等等
- dynamic web server,例如常見的tomcat,jboss,resin等等
對於靜態服務器來講通常就是讀取資源而後返回給browser;動態服務器意味着返回給browser的文件是通過邏輯處理動態產生的。html
- nginx,tomcat這個兩個以前用過,也研究過,因此拿這兩個舉一下示例,不過如今不多用了,如今基本上都是使用node相關的,因此最後構建的serve會基於node。
- 配置簡單,靈活(只有一個主配置文件nginx.conf)
- 支持高併發(靜態小文件)
- 佔用資源相對較少(2w併發,開啓10個線程,內存消耗只有幾百M)
- 功能種類多(例如proxy,cache,Log,Gzip等等)
server { listen 8080; server_name localhost; location / { root html; index index.html index.htm; } }
說明 上面是nginx配置,指定訪問根目錄和默認主頁,以及監聽端口java
➜ ~ clear ➜ ~ curl -i http://127.0.0.1:8080 HTTP/1.1 200 OK Server: nginx/1.12.2 //服務器類型和版本 Date: Fri, 02 Mar 2018 08:49:44 GMT Content-Type: text/html Content-Length: 11 Last-Modified: Fri, 02 Mar 2018 08:46:27 GMT //支持Last-Modified緩存機制 Connection: keep-alive //支持持久鏈接 ETag: "5a990f63-b" //支持ETag緩存機制 Accept-Ranges: bytes // 支持斷點續傳 hello jsdt% //響應體
說明 上面是本地測試請求,從響應頭中能夠看到支持不少功能node
說明 上面是試驗效果nginx
upstream jsdt.com { server 127.0.0.1:8083 max_fails=3 fail_timeout=30s weight=1; server 47.97.xxx.xxx:8084 max_fails=3 fail_timeout=30s weight=2; //爲了安全 隱藏真實ip地址 } server { listen 8080; server_name localhost; location / { root html; # index index.html index.htm; proxy_pass http://jsdt.com; proxy_redirect default; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; }}
說明 上面我摘取了關鍵的部分配置,採用了輪訓+weight算法,其它還有ip_hash、url_hash等算法。真實的應用狀況,還須要考慮不少問題,例如集羣的session同步,記得大學實習期間,當時公司用的是cookie+memcache集羣的方案。git
tomcat運行在jvm上,跨平臺,是一個Servlet容器(能夠運行Servlet,編譯jsp),實現了在http請求響應處理中所須要的http接口相關實現類。除此以外也支持虛擬主機,session共享,靜態文件處理等等,只不過沒那麼專業而已。
說明 如上所示,咱們能夠在頁面中添加動態的處理邏輯,返回的數據根據用戶可定製化(相比靜態服務器優勢),最終.jsp被tomcat編譯爲.java,而後被javac編譯爲通用字節碼文件,最終運行在jvm上。github
在實現本身的服務器以前,首先咱們明確一下server的本質,server屬於應用層的協議,基於tcp的封裝, 而tcp的應用實現是基於socket(不管是node,仍是java都有socket)的封裝。
socket監聽某個端口,獲取面向流的數據data,咱們的server所要作的就是對data進行解析封裝,以使其符合http的規範。
由於有http模塊,因此node當中實現一個基礎server很簡單。可是若是附加額外的功能,例如壓縮,緩存,斷點續傳,反向代理什麼的就須要本身添加了。
接下來首先看一下項目結構,bin目錄主要是放啓動腳本相關的,主邏輯在app.js中,而後根據功能將代碼拆分紅不一樣的模塊。templatet目錄放置編譯的原始模板。web
|____bin | |____.DS_Store | |____deamon.js | |____start | |____yargsConfig.js |____node_modules |____package-lock.json |____package.json |____readme.md |____src | |____.DS_Store | |____app.js | |____asset | |____cacheSupport.js | |____config.js | |____picGuard.js | |____template | |____util.js
在server運行前,首先咱們經過yargs模塊獲取解析好的命令行參數。以下所示算法
if(argv.D){ let sp = cp.spawn(process.execPath, ['deamon.js'],{ cwd: __dirname, stdio: ['ignore','ignore','ignore'], env: argv, detached: true //http://nodejs.cn/api/child_process.html#child_process_child_process_spawn_command_args_options } ) sp.unref() } else { let config = Object.assign({}, defautConfig, argv) let server = new Server(config); server.start(); console.log('server already started') }
說明 若是開啓deamon模式,則經過子進程的方式讓服務在後臺運行,反之則直接啓動server實例apache
在啓動server以後,開始接受並處理請求,下面以斷點續傳功能模塊做爲示例
function byteRangeStream(req, res, filepath, statObj) { let start = 0 let end = statObj.size-1 let range = req.headers['range'] if (range){ res.setHeader('Accept-Range','bytes') res.statusCode = 206 //a part of content let result = range.match(/bytes=(\d*)-(\d*)/); if (result) { start = isNaN(result[1]) ? start : parseInt(result[1]); end = isNaN(result[2]) ? end : parseInt(result[2]) - 1; } } return fs.createReadStream(filepath,{ start, end }) } module.exports ={ byteRangeStream }
說明 在主模塊app.js中,導入上述模塊,如代碼中所示首先判斷客戶端是否支持斷點續傳,依據range請求頭,若是有請求範圍,直接返回請求範圍內的數據,不然所有讀取返回,靠的是browser和server的協商機制,須要雙方都支持才能完成整個過程。
更多功能模塊能夠參考個人github, 歡迎star。
寫這篇文章,總結了下server的相關知識,參考了以前大學時作的筆記,看到以前作的記錄,回憶當時在學校學習和公司實習的經歷,感慨萬千。時光易逝,作好當下的本身。