สร้าง Web Server อย่างง่าย
เราได้พูดคุยกันแล้วว่าเว็บแอปพลิเคชันนั้นใช้โปรโตคอล HTTP และ Go ให้การสนับสนุน HTTP เต็มรูปแบบในแพ็คเกจ net/http
ง่ายมากในการตั้งค่าเว็บเซิร์ฟเวอร์โดยใช้แพ็คเกจนี้
ข้อกำหนดเบื้องต้น
ข้อกำหนดเพียงอย่างเดียวสำหรับบทความนี้คือ คุณต้องติดตั้ง Go บนคอมพิวเตอร์ของคุณและเคยทำตามบทความ GOPATH และ Go Workspace มาก่อน
ใช้แพ็คเกจ http ตั้งค่าเว็บเซิร์ฟเวอร์
สร้างโฟลเดอร์โมดูล ชื่อ web สร้าง ไฟล์โก ชื่อ main.go เขียนโค้ดดังนี้
package main
import (
"fmt"
"net/http"
"strings"
"log"
)
func sayhelloName(w http.ResponseWriter, r *http.Request) {
r.ParseForm() // parse arguments, you have to call this by yourself
fmt.Println(r.Form) // print form information in server side
fmt.Println("path", r.URL.Path)
fmt.Println("scheme", r.URL.Scheme)
fmt.Println(r.Form["url_long"])
for k, v := range r.Form {
fmt.Println("key:", k)
fmt.Println("val:", strings.Join(v, ""))
}
fmt.Fprintf(w, "Hello astaxie!") // send data to client side
}
func main() {
http.HandleFunc("/", sayhelloName) // set router
err := http.ListenAndServe(":9090", nil) // set listen port
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
File -> Auto Save
คลิกขวาที่ โฟลเดอร์ web -> Open integrated Terminal
สร้าง Go Modules โดยใช้คำสั่ง
go mod init
คอมไพล์ไฟล์ซอร์ส go build
go build
ใช้งานโหมด “Command Prompt” โดยไปที่ ช่องค้นหา พิมพ์ cmd เลือก Command Prompt
ไปที่ GOPATH ของเราในตัวอย่างคือ c:\mygo
เข้าไปยังโฟลเดอร์ web
แล้วเรียกใช้งานเป็น web.exe -> Enter
หลังจากที่เรารันโค้ดข้างต้นแล้ว เซิร์ฟเวอร์จะเริ่มฟังพอร์ต 9090 ใน local host.
เปิดเว็บบราวเซอร์ ป้อน URL http://localhost:9090 คุณจะเห็นว่า Hello astaxie อยู่บนหน้าจอของคุณ
ลองใช้ที่อยู่อื่นที่มีอาร์กิวเมนต์เพิ่มเติมเป็น: http://localhost:9090/?url_long=111&url_long=222
ตอนนี้เรามาดูกันว่าเกิดอะไรขึ้นกับทั้งฝั่งไคลเอ็นต์และฝั่งเซิร์ฟเวอร์
ฝั่งไคลเอ็นต์
คุณควรเห็นข้อมูลต่อไปนี้ที่ฝั่งเซิร์ฟเวอร์:
อย่างที่คุณเห็น เราจำเป็นต้องเรียกใช้สองฟังก์ชันเท่านั้นเพื่อสร้างเว็บเซิร์ฟเวอร์อย่างง่าย
หากคุณกำลังทำงานกับ PHP คุณอาจกำลังถามว่าเราต้องการบางอย่างเช่น Nginx หรือ Apache หรือไม่ คำตอบคือ เราไม่รับ เนื่องจาก Go รับฟังพอร์ต TCP ด้วยตัวเอง และฟังก์ชัน sayhelloName
นี้เป็นฟังก์ชันลอจิกเหมือนกับตัวควบคุมใน PHP
หากคุณกำลังทำงานกับ Python คุณควรรู้เกี่ยวกับ tornado และตัวอย่างข้างต้นก็คล้ายกันมาก
หากคุณกำลังทำงานกับ Ruby คุณอาจสังเกตเห็นว่ามันเหมือนกับสคริปต์/เซิร์ฟเวอร์ใน ROR (Ruby on Rails)
เราใช้ฟังก์ชันง่ายๆ สองฟังก์ชันในการตั้งค่าเว็บเซิร์ฟเวอร์อย่างง่ายในส่วนนี้ และเซิร์ฟเวอร์แบบธรรมดานี้มีความจุสำหรับการดำเนินการพร้อมกันในระดับสูงอยู่แล้ว
Go ทำงานอย่างไรกับเว็บ
เราเรียนรู้การใช้แพ็คเกจ net/http
เพื่อสร้างเว็บเซิร์ฟเวอร์อย่างง่ายในส่วนที่แล้ว และหลักการทำงานทั้งหมดนั้นเหมือนกับที่เราจะพูดถึงในส่วนแรกของบทนี้
แนวคิดในหลักการของเว็บ
คำขอ: ขอข้อมูลจากผู้ใช้ รวมทั้ง POST, GET, Cookie และ URL
ตอบกลับ: ข้อมูลตอบกลับจากเซิร์ฟเวอร์ไปยังไคลเอนต์
Conn: การเชื่อมต่อระหว่างไคลเอนต์และเซิร์ฟเวอร์
ตัวจัดการ: ขอตรรกะการจัดการและการสร้างการตอบสนอง
กลไกการทำงานของแพ็คเกจ http
รูปภาพต่อไปนี้แสดงขั้นตอนการทำงานของเว็บเซิร์ฟเวอร์ Go
- สร้างซ็อกเก็ตการฟัง ฟังพอร์ต และรอลูกค้า
- ยอมรับคำขอจากลูกค้า
- จัดการคำขอ อ่านส่วนหัว HTTP หากคำขอใช้วิธี POST ให้อ่านข้อมูลในเนื้อหาของข้อความและส่งต่อไปยังตัวจัดการ สุดท้าย socket ส่งคืนข้อมูลการตอบกลับไปยังไคลเอนต์
เมื่อเราทราบคำตอบของคำถามสามข้อต่อไปนี้แล้ว ก็จะรู้ว่าเว็บทำงานอย่างไรใน Go
- เราจะฟังพอร์ตได้อย่างไร?
- เราจะยอมรับคำขอของลูกค้าได้อย่างไร
- เราจะจัดสรรตัวจัดการอย่างไร?
ในส่วนก่อนหน้านี้ เราเห็นว่า Go ใช้ListenAndServe
เพื่อจัดการขั้นตอนเหล่านี้: เริ่มต้นวัตถุเซิร์ฟเวอร์ เรียกnet.Listen("tcp", addr)
เพื่อตั้งค่า TCP listener และฟังที่อยู่และพอร์ตเฉพาะ
มาดูซอร์สโค้ดของแพ็คเกจ http
กัน
//Build version go1.1.2.
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
var tempDelay time.Duration // how long to sleep on accept failure
for {
rw, e := l.Accept()
if e != nil {
if ne, ok := e.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
return e
}
tempDelay = 0
c, err := srv.newConn(rw)
if err != nil {
continue
}
go c.serve()
}
}
เราจะยอมรับคำขอของลูกค้าหลังจากที่เราเริ่มฟังพอร์ตได้อย่างไร ในซอร์สโค้ด เราจะเห็นว่าsrv.Serve(net.Listener)
มีการเรียกเพื่อจัดการกับคำขอของลูกค้า ในร่างกายของฟังก์ชันมีfor{}
. ยอมรับคำขอ สร้างการเชื่อมต่อใหม่ จากนั้นเริ่ม goroutine ใหม่ ส่งข้อมูลคำขอไปยังgo c.serve()
goroutine นี่คือวิธีที่ Go รองรับการทำงานพร้อมกันสูงและทุก goroutine เป็นอิสระ
เราจะใช้ฟังก์ชันเฉพาะเพื่อจัดการกับคำขอได้อย่างไรconn
แยกวิเคราะห์ขอc.ReadRequest()
ในตอนแรกนั้นได้รับการจัดการที่สอดคล้องกัน: ซึ่งเป็นอาร์กิวเมนต์ที่สองเราผ่านเมื่อเราเรียกว่าhandler := sh.srv.Handler
ListenAndServe
เพราะเราผ่านไปใช้ดำเนินการเริ่มต้นของnil
handler = DefaultServeMux
แล้วมาDefaultServeMux
ทำอะไรที่นี่? มันเป็นตัวแปรของเราเตอร์ที่สามารถเรียกใช้ฟังก์ชันตัวจัดการสำหรับ URL เฉพาะ เราตั้งค่านี้หรือไม่? ใช่เราทำ. เราทำสิ่งนี้ในบรรทัดแรกที่เราใช้http.HandleFunc("/", sayhelloName)
. เรากำลังใช้ฟังก์ชันนี้เพื่อลงทะเบียนกฎของเราเตอร์สำหรับพาธ “/” เมื่อ URL ที่เป็นเราเตอร์เรียกฟังก์ชัน/
sayhelloName
DefaultServeMux เรียก ServerHTTP เพื่อรับฟังก์ชันตัวจัดการสำหรับพาธต่างๆ เรียกsayhelloName
ในกรณีเฉพาะนี้ สุดท้าย เซิร์ฟเวอร์จะเขียนข้อมูลและตอบสนองต่อลูกค้า
ขั้นตอนการทำงานโดยละเอียด: