<b>1.7 一個web伺服器</b>
<b></b>
使用go的庫非常容易實作一個web伺服器,用來響應像fetch那樣的用戶端請求。本節将展示一個迷你伺服器,傳回通路伺服器的url的路徑部分。例如,如果請求的url是http://localhost:8000/hello,響應将是url.path = "/hello"。
這個程式隻有寥寥幾行代碼,因為庫函數做了大部分工作。main函數将一個處理函數和以/開頭的url連結在一起,代表所有的url使用這個函數處理,然後啟動伺服器監聽進入8000端口處的請求。一個請求由一個http.request類型的結構體表示,它包含很多關聯的域,其中一個是所請求的url。當一個請求到達時,它被轉交給處理函數,并從請求的url中提取路徑部分(/hello),使用fmt.printf格式化,然後作為響應發送回去。web伺服器将在7.7節進行詳細讨論。
讓我們在背景啟動伺服器。在mac os x或者linux上,在指令行後添加一個&符号;在微軟windows上,不需要&符号,而需要單獨開啟一個獨立的指令行視窗。
可以從指令行發起客戶請求:
另外,還可以通過浏覽器進行通路,如圖1-2所示。
為伺服器添加功能很容易。一個有用的擴充是一個特定的url,它傳回某種排序的狀态。例如,這個版本的程式完成和回聲伺服器一樣的事情,但同時傳回請求的數量;url
/count請求傳回到現在為止的個數,去掉/count請求本身:
這個伺服器有兩個處理函數,通過請求的url來決定哪一個被調用:請求/count調用counter,其他的調用handler。以/結尾的處理模式比對所有含有這個字首的url。在背景,對于每個傳入的請求,伺服器在不同的goroutine中運作該處理函數,這樣它可以同時處理多個請求。然而,如果兩個并發的請求試圖同時更新計數值count,它可能會不一緻地增加,程式會産生一個嚴重的競态bug(參考9.1節)。為避免該問題,必須確定最多隻有一個goroutine在同一時間通路變量,這正是mu.lock()和mu.unlock()語句的作用。第9章将更細緻地讨論共享變量的并發通路。
作為一個更完整的例子,處理函數可以報告它接收到的消息頭和表單資料,這樣可以友善伺服器審查和調試請求:
這裡使用http.request結構體的成員來産生類似下面的輸出:
注意這裡是如何在if語句中嵌套調用parseform的。go允許一個簡單的語句(如一個局部變量聲明)跟在if條件的前面,這在錯誤處理的時候特别有用。也可以這樣寫:
但是合并的語句更短而且可以縮小err變量的作用域,這是一個好的實踐。2.7節将介紹作用域。
這些程式中,我們看到了作為輸出流的三種非常不同的類型。fetch程式複制http響應到檔案os.stdout,像lissajous一樣;fetchall程式通過将響應複制到ioutil.discard中進行丢棄(在統計其長度時);web伺服器使用fmt.fprintf通過寫入http.responsewriter來讓浏覽器顯示。
盡管三種類型細節不同,但都滿足一個通用的接口(interface),該接口允許它們按需使用任何一種輸出流。該接口(稱為io.writer)将在7.1節進行讨論。
go的接口機制是第7章的内容,但是為了說明它可以做什麼,我們來看一下整合web伺服器和lissajous函數是一件多麼容易的事情,這樣gif動畫将不再輸出到标準輸出而是http用戶端。簡單添加這些行到web伺服器:
或者也可以:
上面handlefunc函數中立即調用的第二個參數是函數字面量,這是一個在該場景中使用它時才定義的匿名函數,這将在5.6節進一步解釋。
一旦你完成這個改變,就可以通過浏覽器通路http://localhost:8000。每次加載頁面,你将看到一個類似圖1-3的動畫。
練習1.12:修改利薩茹伺服器以通過url參數讀取參數值。例如,你可以通過調整它,使得像http://localhost:8000/?cycles=20這樣的網址将其周期設定為20,以替代預設的5。使用strconv.atoi函數來将字元串參數轉化為整型。可以通過go doc strconv.atoi來檢視文檔。