對於普通的上網過程,系統實際上是這樣作的:瀏覽器自己是一個客戶端,當輸入 URL 請求網頁時,首先瀏覽器會去請求 DNS 服務器,獲取與域名對應的 IP,而後經過 IP 地址找到對應的服務器後,要求創建 TCP 鏈接,等瀏覽器發送完 HTTP Request (請求)包後,服務器接收到請求包以後纔開始處理請求包,服務器調用自身服務,返回 HTTP Response(響應)包;客戶端收到來自服務器的響應後開始渲染這個 Response 包 裏的主體(body),等收到所有的內容隨後斷開與該服務器之間的 TCP 鏈接。html
以上 Web 工做方式能夠簡單地概括爲:web
服務器端的幾個概念:編程
如下是 http 包執行流程:後端
Go 語言的標準庫 net/http 提供了 http 編程有關的接口,封裝了內部 TCP 鏈接和報文解析的複雜瑣碎的細節,使用者只須要和 http.request 、 http.ResponseWriter 這兩個對象交互就行。也就是說,只要寫一個 handler,請求會經過參數傳遞進來,而它要作的就是根據請求的數據作處理,把結果寫到 Response 中。
瀏覽器
package main
import (
"fmt"
"log"
"net/http"
)
// HelloServer 函數實現了處理器的簽名,因此這是一個處理器函數
func HelloServer(w http.ResponseWriter,r *http.Request) {
fmt.Println("path:", r.URL.Path)
fmt.Fprintf(w, "Hello Go Web")
}
func main() {
// 註冊路由和路由函數,將url規則與處理器函數綁定作一個map映射存起來,而且會實現ServeHTTP方法,使處理器函數變成Handler函數
http.HandleFunc("/",HelloServer)
fmt.Println("服務器已經啓動,請在瀏覽器地址欄中輸入 http://localhost:8900")
// 啓動 HTTP 服務,並監聽端口號,開始監聽,處理請求,返回響應
err := http.ListenAndServe(":8900", nil)
fmt.Println("監聽以後")
if err != nil {
log.Fatal("ListenAndServe",err)
}
}
複製代碼
執行以上程序後,在瀏覽器輸入 http://localhost:8900,在控制檯輸出:安全
服務器已經啓動,請在瀏覽器地址欄中輸入 http://localhost:8900
path: /
複製代碼
瀏覽器訪問的網頁顯示:Hello Go Webbash
在實際開發中,HTTP 接口會有許多的 URL 和對應的 Handler。這裏就要用到 net/http 的 ServeMux。Mux是 multiplexor 的縮寫,就是多路傳輸的意思(請求傳過來,根據某種判斷,分流到後端多個不一樣的地方)。ServeMux 能夠註冊多了 URL 和 handler 的對應關係,並自動把請求轉發到對應的 handler 進行處理。服務器
Go 語言實現一個 web 路由主要作三件事:cookie
如下是實現一個簡易的 http 路由示例:
WebServerRoute.go網絡
package main
import (
"fmt"
"log"
"net/http"
)
// 首頁處理器
func HomeHandler (w http.ResponseWriter, r *http.Request) {
fmt.Println("path:", r.URL.Path)
fmt.Fprintf(w, "Welcome to home")
}
// 註冊頁處理器
func registerHandler (w http.ResponseWriter, r *http.Request) {
fmt.Println("path:", r.URL.Path)
fmt.Fprintf(w, "Welcome to register")
}
// 登陸頁處理器
func loginHandler (w http.ResponseWriter, r *http.Request) {
fmt.Println("path:", r.URL.Path)
fmt.Fprintf(w, "Welcome to login")
}
func main() {
// 路由:/home -- 首頁
http.HandleFunc("/home",loginHandler)
// 路由:/register -- 註冊頁
http.HandleFunc("/register",registerHandler)
// 路由:/login -- 登陸頁
http.HandleFunc("/login",loginHandler)
fmt.Println("服務器已經啓動,訪問:\n首頁地址:http://localhost:8900/home\n註冊頁地址:http://localhost:8900/register\n登陸頁地址:http://localhost:8900/login")
err := http.ListenAndServe(":8900", nil)
fmt.Println("監聽以後")
if err != nil {
log.Fatal("ListenAndServe",err)
}
}
複製代碼
執行以上程序後,在瀏覽器先後輸入
http://localhost:8900/home
http://localhost:8900/register
http://localhost:8900/login
在控制檯輸出:
服務器已經啓動,訪問:
首頁地址:http://localhost:8900/home
註冊頁地址:http://localhost:8900/register
登陸頁地址:http://localhost:8900/login
path: /home
path: /register
path: /login
複製代碼
瀏覽器訪問的網頁先後分別顯示:
Welcome to home
Welcome to register
Welcome to login
如下示例獲取 HTTP 請求頭: Path、Host、Method(Get、post)、Proto、UserAgent 等信息
RequestInfo.go
package main
import (
"fmt"
"log"
"net/http"
)
// HelloServer2 函數實現了處理器的簽名,因此這是一個處理器函數
func HelloServer2(w http.ResponseWriter,r *http.Request) {
fmt.Println("path:", r.URL.Path)
fmt.Println("Url:",r.URL)
fmt.Println("Host:",r.Host)
fmt.Println("Header:",r.Header)
fmt.Println("Method:",r.Method)
fmt.Println("Proto:",r.Proto)
fmt.Println("UserAgent:",r.UserAgent())
fmt.Fprintf(w, "Hello Go Web")
}
func main() {
// 註冊路由和路由函數,將url規則與處理器函數綁定作一個map映射存起來,而且會實現ServeHTTP方法,使處理器函數變成Handler函數
http.HandleFunc("/",HelloServer2)
fmt.Println("服務器已經啓動,請在瀏覽器地址欄中輸入 http://localhost:8900/a/b")
// 啓動 HTTP 服務,並監聽端口號,開始監聽,處理請求,返回響應
err := http.ListenAndServe(":8900", nil)
fmt.Println("監聽以後")
if err != nil {
log.Fatal("ListenAndServe",err)
}
}
複製代碼
執行以上程序後,在瀏覽器輸入 http://localhost:8900/a/b , 在控制檯輸出:
服務器已經啓動,請在瀏覽器地址欄中輸入 http://localhost:8900/a/b
path: /a/b
Url: /a/b
Host: localhost:8900
Header: map[Accept:[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3] Accept-Encoding:[gzip, deflate, br] Cookie:[UM_distinctid=1682d397d27566-07ba4cbda8d037-2d604637-4a640-1682d397d2a745; _ga=GA1.1.301532435.1546946971; Hm_lvt_ac60c3773958d997a64b55feababb4a1=1547204629] Upgrade-Insecure-Requests:[1] User-Agent:[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36] Accept-Language:[en,zh-CN;q=0.9,zh;q=0.8] Connection:[keep-alive] Cache-Control:[max-age=0]]
Method: GET
Proto: HTTP/1.1
UserAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
複製代碼
瀏覽器訪問的網頁顯示:Hello Go Web
完整的請求路徑由 scheme(http/https) + 域名或IP(localhost) + 端口號 + Path 組成,示例以下:
FullRequestPath.go
package main
import (
"fmt"
"log"
"net/http"
"strings"
)
/*
獲取完整的請求路徑
http://localhost:8900/a/b/x.html
1. scheme: http/https
2. 域名或IP: localhost
3. 端口號: 8900
4. Path: /a/b/x.html
*/
// HelloServer 函數實現了處理器的簽名,因此這是一個處理器函數
func HelloServer3(w http.ResponseWriter,r *http.Request) {
scheme := "http://"
if r.TLS != nil {
scheme = "https://"
}
fmt.Println("scheme:", scheme)
fmt.Println("域名(IP)和端口號:", r.Host)
fmt.Println("Path:", r.RequestURI)
fmt.Println("完整的請求路徑:", strings.Join([]string{scheme,r.Host,r.RequestURI},""))
fmt.Fprintf(w, "Hello Go Web")
}
func main() {
// 註冊路由和路由函數,將url規則與處理器函數綁定作一個map映射存起來,而且會實現ServeHTTP方法,使處理器函數變成Handler函數
http.HandleFunc("/",HelloServer3)
fmt.Println("服務器已經啓動,請在瀏覽器地址欄中輸入 http://localhost:8900/a/b/x.html")
// 啓動 HTTP 服務,並監聽端口號,開始監聽,處理請求,返回響應
err := http.ListenAndServe(":8900", nil)
fmt.Println("監聽以後")
if err != nil {
log.Fatal("ListenAndServe",err)
}
}
複製代碼
執行以上程序後,在瀏覽器輸入 http://localhost:8900/a/b/x.html ,在控制檯輸出:
服務器已經啓動,請在瀏覽器地址欄中輸入 http://localhost:8900/a/b/x.html
scheme: http://
域名(IP)和端口號: localhost:8900
Path: /a/b/x.html
完整的請求路徑: http://localhost:8900/a/b/x.html
複製代碼
瀏覽器訪問的網頁顯示:Hello Go Web
HTTP 服務器不一樣於 HTTPS 服務器,HTTP 協議是明文的,HTTPS 協議(HTTP over SSL 或 HTTP over TLS )是密文的。
使用如下 openssl 方式手動生成 SSL 證書:
生成密鑰文件命令:
openssl genrsa -out server.key 2048
複製代碼
結合已經生成的密鑰文件生成證書csr文件命令:
openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
複製代碼
在生成csr文件的過程當中,會提示你輸入證書所要求的字段信息,包括國家(中國添CN)、省份、所在城市、單位名稱、單位部門名稱(能夠不填直接回車)。請注意: 除國家縮寫必須填CN外,其他均可以是英文或中文。
HttpsServer.go 文件代碼以下:
package main
import (
"fmt"
"log"
"net/http"
"strings"
)
/*
編寫 HTTPS 服務器
HTTPS = HTTP + Secure(安全)
RSA 進行加密
SHA 進行驗證
密鑰和證書
生成密鑰文件
openssl genrsa -out server.key 2048
生成證書文件
openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
*/
func httpsServer(w http.ResponseWriter, r *http.Request) {
fmt.Println("path:", r.URL.Path)
fmt.Println("Url:",r.URL)
fmt.Println("Host:",r.Host)
fmt.Println("Header:",r.Header)
fmt.Println("Method:",r.Method)
fmt.Println("Proto:",r.Proto)
fmt.Println("UserAgent:",r.UserAgent())
scheme := "http://"
if r.TLS != nil {
scheme = "https://"
}
fmt.Println("完整的請求路徑:", strings.Join([]string{scheme,r.Host,r.RequestURI},""))
fmt.Fprintf(w, "Hello Go Web")
}
func main() {
http.HandleFunc("/",httpsServer)
fmt.Println("HTTPS 服務器已經啓動,請在瀏覽器地址欄中輸入 https://localhost:4321/")
err := http.ListenAndServeTLS(":4321","/Users/play/goweb/src/basic/server.crt","/Users/play/goweb/src/basic/server.key",nil)
if err != nil {
log.Fatal("ListenAndServe",err)
}
}
複製代碼
執行以上程序後,在瀏覽器輸入 https://localhost:4321/ ,在控制檯輸出:
HTTPS 服務器已經啓動,請在瀏覽器地址欄中輸入 https://localhost:4321/
2019/07/06 19:35:55 http: TLS handshake error from [::1]:58945: remote error: tls: unknown certificate
path: /
Url: /
Host: localhost:4321
Header: map[Cache-Control:[max-age=0] Upgrade-Insecure-Requests:[1] User-Agent:[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36] Accept:[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3] Accept-Encoding:[gzip, deflate, br] Accept-Language:[en,zh-CN;q=0.9,zh;q=0.8] Cookie:[UM_distinctid=1682d397d27566-07ba4cbda8d037-2d604637-4a640-1682d397d2a745; _ga=GA1.1.301532435.1546946971; Hm_lvt_ac60c3773958d997a64b55feababb4a1=1547204629]]
Method: GET
Proto: HTTP/2.0
UserAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
完整的請求路徑: https://localhost:4321/
複製代碼
瀏覽器訪問的網頁顯示:Hello Go Web。 由於以上申請的 SSL 證書沒有進行認證,因此會提示"unknown certificate",未認證證書只能用於開發測試。