สร้าง 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


  1. สร้างซ็อกเก็ตการฟัง ฟังพอร์ต และรอลูกค้า
  2. ยอมรับคำขอจากลูกค้า
  3. จัดการคำขอ อ่านส่วนหัว 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 ที่เป็นเราเตอร์เรียกฟังก์ชัน/ sayhelloNameDefaultServeMux เรียก ServerHTTP เพื่อรับฟังก์ชันตัวจัดการสำหรับพาธต่างๆ เรียกsayhelloNameในกรณีเฉพาะนี้ สุดท้าย เซิร์ฟเวอร์จะเขียนข้อมูลและตอบสนองต่อลูกค้า

ขั้นตอนการทำงานโดยละเอียด:

Leave a Reply

Your email address will not be published. Required fields are marked *