วิธีสร้างเว็บแอปพลิเคชันแรกของคุณด้วย Go
Go เหมาะสำหรับการพัฒนาเว็บ มันมีประสิทธิภาพที่ยอดเยี่ยม ง่ายต่อการปรับใช้ และมีเครื่องมือที่จำเป็นมากมายที่คุณต้องการเพื่อสร้างและปรับใช้บริการเว็บที่สมบูรณ์ในไลบรารีมาตรฐาน บทความนี้จะแนะนำคุณเกี่ยวกับตัวอย่างที่ใช้งานได้จริงของการสร้างและการปรับใช้เว็บแอปพลิเคชันในภาษา Go
ในบทความนี้ คุณจะค้นพบวิธีใช้ประโยชน์จาก Go สำหรับการพัฒนาเว็บโดยการสร้างแอปพลิเคชั่นข่าวด้วยภาษา Go เป็นแอปง่าย ๆ ที่ดึงบทความข่าวที่ตรงกับคำค้นหาเฉพาะผ่าน News API และนำเสนอผลลัพธ์บนหน้าเว็บ
ข้อกำหนดเบื้องต้น
ข้อกำหนดเพียงอย่างเดียวสำหรับบทความนี้คือ คุณต้องติดตั้ง Go บนคอมพิวเตอร์ของคุณตามบทความ ติดตั้ง Go และ ทดสอบ Hello World และ คุณคุ้นเคยกับรูปแบบและโครงสร้างของมัน ด้วยการผ่านการเรียนรู้บทความ Workshop-2 New Deck & Print มาก่อน
Grab ไฟล์เริ่มต้น
โคลน Clone the starter files repo on GitHub และ cd ในไดเร็กทอรีที่สร้างขึ้น เรามีไฟล์หลักสามไฟล์ในไดเร็กทอรีนี้คือ main.goไฟล์เป็นที่ที่เราจะเขียนโค้ดแอปพลิเคชัน และไฟล์ index.html คือเทมเพลตที่จะถูกส่งไปยังเบราว์เซอร์ ในขณะที่ assets/styles.css เป็นไฟล์ที่มีสไตล์ เพื่อเพิ่มความสวยงามให้หน้าเว็บของเรา
$ git clone https://github.com/Freshman-tech/news-demo-starter-files
$ cd news-demo-starter-files
$ tree
.
├── assets
│ └── style.css
├── index.html
├── LICENCE
├── main.go
└── README.md
ไปที่แท็บ TERMINAL แล้ว cd เข้าไปในโฟลเดอร์ src พิมพ์คำสั่ง git clone https://github.com/Freshman-tech/news-demo-starter-files แล้ว Enter
จะมีโฟลเดอร์ news-demo-starter-files เพิ่มเข้ามา
สร้างเว็บเซิร์ฟเวอร์พื้นฐาน
เริ่มต้นด้วยการสร้างเซิร์ฟเวอร์พื้นฐานที่ส่งข้อความ “Hello World!” ไปยังเบราว์เซอร์เมื่อมีการส่งคำขอ GET ไปยังรูทเซิร์ฟเวอร์ โดยแก้ไขไฟล์ main.go ของคุณตามโค้ดด้านล่าง:
package main
import (
"net/http"
"os"
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("<h1>Hello World!</h1>"))
}
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
mux := http.NewServeMux()
mux.HandleFunc("/", indexHandler)
http.ListenAndServe(":"+port, mux)
}
บรรทัดแรก package main ประกาศว่าโค้ดในไฟล์ main.go เป็นของแพ็คเกจ main
package main
ในสองสามบรรทัดถัดไปแพ็คเกจ the net/http และ os จะถูกนำเข้าไปยังไฟล์ แพ็คเกจ net/http คือมีการใช้งานไคลเอ็นต์ HTTP และเซิร์ฟเวอร์เพื่อใช้ในแอปของเรา ในขณะที่วิธีหลัง os เป็นวิธีที่เราสามารถเข้าถึงฟังก์ชันของระบบปฏิบัติการได้
import (
"net/http"
"os"
)
ในฟังก์ชัน main มีการพยายามตั้งค่าตัวแปร port ตามค่าสภาพแวดล้อมของตัวแปร PORT หากไม่มีตัวแปร Getenv() จะคืนค่าสตริงว่างและตั้งค่าพอร์ตเป็น 3000 เพื่อให้เซิร์ฟเวอร์พร้อมใช้งานที่ http://localhost:3000
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
ถัดไป http.NewServeMux() เมธอดนี้ใช้เพื่อสร้างมัลติเพล็กเซอร์คำขอ HTTP ซึ่งถูกกำหนดให้กับตัวแปร mux ในภายหลัง โดยพื้นฐานแล้ว คำขอมัลติเพล็กเซอร์จะจับคู่กับ URL ของคำขอขาเข้ากับรายการรูปแบบที่ลงทะเบียน และเรียกตัวจัดการที่เกี่ยวข้องสำหรับทุกครั้งที่พบการจับคู่
mux := http.NewServeMux()
การลงทะเบียนตัวจัดการคำขอ HTTP ทำได้โดยใช้วิธี HandleFunc ซึ่งใช้สตริงรูปแบบเป็นอาร์กิวเมนต์แรก และฟังก์ชันที่มี signature func ต่อไปนี้
func indexHandler(w http.ResponseWriter, r *http.Request)
หากคุณดูที่ฟังก์ชัน indexHandler คุณจะเห็นว่าฟังก์ชันนี้มี signature func แน่นอน ทำให้เป็นอาร์กิวเมนต์ที่สองที่ถูกต้องสำหรับ HandleFunc และ พารามิเตอร์ w เป็นโครงสร้างที่เราใช้ในการตอบสนองการส่งต่อคำขอไปยัง HTTP โดยใช้ Write() วิธีการที่ยอมรับส่วนของไบต์และเขียนข้อมูลไปยังการเชื่อมต่อซึ่งเป็นส่วนหนึ่งของการตอบสนอง HTTP
ในทางกลับกันพารามิเตอร์ r แสดงถึงคำขอ HTTP ที่ได้รับจากลูกค้า เป็นวิธีที่เราเข้าถึงข้อมูลที่ส่งโดยเว็บเบราว์เซอร์บนเซิร์ฟเวอร์ เรายังไม่ได้ใช้มันที่นี่ แต่เราจะใช้มันในภายหลังอย่างแน่นอน
สุดท้าย เรามี http.ListenAndServe() วิธีการที่เริ่มต้นเซิร์ฟเวอร์บนพอร์ตที่กำหนดโดยตัวแปร port คุณสามารถใช้พอร์ตอื่นได้หากมีการใช้งาน localhost:3000 บนเครื่องของคุณแล้ว
ต่อไป ให้คอมไพล์และรันโค้ดที่คุณเพิ่งเขียน:
คลิกขวาที่โฟลเดอร์ news-demo-starter-files -> Open integrated Terminal
ใช้คำสั่ง go build จะมีไฟล์ news-demo-starter-files.exe เพิ่มเข้ามา
ทดสอบการทำงานโดย ใช้คำสั่ง ./news-demo-starter-files
หากคุณไปที่ http://localhost:3000 ในเบราว์เซอร์ คุณจะเห็นข้อความ “Hello World!” แสดงบนหน้าจอของคุณ
ตัวแปร Environmental
รูปแบบทั่วไปเกี่ยวกับตัวแปรสภาพแวดล้อมคือการโหลดจากไฟล์ .env สู่สภาพแวดล้อม godotenv เป็นพอร์ต Go ของไลบรารี Ruby dotenv ช่วยให้คุณสามารถกำหนดตัวแปร Environmental ของแอปพลิเคชันของคุณในไฟล์ .env และโหลดลงในสภาพแวดล้อมเมื่อเริ่มต้นโปรแกรม
สิ่งแรกที่คุณต้องทำคือสร้าง .env ไฟล์ในรูทของไดเร็กทอรีในโปรเจคของคุณ:
ที่ โฟลเดอร์ news-demo-starter-file โดยคลิกไปที่ไอคอน New File สร้างไฟล์ชื่อ “.env”
จากนั้นเปิดไฟล์ ตั้งค่า PORT ตัวแปร environmental
PORT=3000
ถัดไป ติดตั้งแพ็คเกจ godotenv โดยรันคำสั่งต่อไปนี้ในไดเร็กทอรีโครงการของคุณ:
go get github.com/joho/godotenv
จากนั้นอัปเดตไฟล์ main.go ของคุณดังที่แสดงด้านล่าง:
package main
import (
"log"
"net/http"
"os"
"github.com/joho/godotenv"
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("<h1>Hello World 2 !</h1>"))
}
func main() {
err := godotenv.Load()
if err != nil {
log.Println("Error loading .env file")
}
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
mux := http.NewServeMux()
mux.HandleFunc("/", indexHandler)
http.ListenAndServe(":"+port, mux)
}
Load วิธีการอ่าน .env ไฟล์และโหลดตัวแปรชุดเข้ากับสภาพแวดล้อมเพื่อให้พวกเขาสามารถเข้าถึงได้ผ่านเมธอด os.Getenv() สิ่งนี้มีประโยชน์อย่างยิ่งในการจัดเก็บข้อมูล ที่เป็นความลับในสภาพแวดล้อม
หยุดกระบวนการทำงานในเทอร์มินัลของคุณโดยใช้ ctrl-c จากนั้นสร้างและเรียกใช้เว็บเซิร์ฟเวอร์อีกครั้งด้วย คำสั่ง go build และ ./news-demo-starter-files
จากนั้นรีเฟรชเบราว์เซอร์ของคุณ คุณควรเห็นข้อความ “Hello World 2 !” บนหน้าดังที่แสดงด้านล่าง:
เทมเพลตใน Go
มาดูพื้นฐานของการสร้างเทมเพลตใน Go กัน หากคุณคุ้นเคยกับเทมเพลตในภาษาอื่นๆ จะเข้าใจได้ง่าย
เทมเพลตเป็นวิธีที่ง่ายในการปรับแต่งผลลัพธ์ของเว็บแอปพลิเคชันของคุณโดยขึ้นอยู่กับเส้นทางโดยไม่ต้องเขียนโค้ดเดียวกันในหลายๆ ที่ ตัวอย่างเช่น เราสามารถสร้างเทมเพลตสำหรับแถบนำทางและใช้กับทุกหน้าของไซต์โดยไม่ต้องสร้างโค้ดซ้ำ ยิ่งไปกว่านั้น เรายังสามารถเพิ่มตรรกะพื้นฐานให้กับหน้าเว็บของเราได้อีกด้วย
Go มีไลบรารีเทมเพลตสองไลบรารีในไลบรารีมาตรฐาน คือ : text/template และ html/template ทั้งสองมีอินเทอร์เฟซเดียวกัน อย่างไรก็ตาม แพ็คเกจ html/template ถูกใช้เพื่อสร้างเอาต์พุต HTML ที่ปลอดภัยต่อการเขียนโค้ด ดังนั้นเราจะนำไปใช้
นำเข้าแพ็คเกจนี้ในไฟล์ main.go ของคุณและใช้ดังนี้:
package main
import (
"html/template"
"log"
"net/http"
"os"
"github.com/joho/godotenv"
)
var tpl = template.Must(template.ParseFiles("index.html"))
func indexHandler(w http.ResponseWriter, r *http.Request) {
tpl.Execute(w, nil)
}
func main() {
err := godotenv.Load()
if err != nil {
log.Println("Error loading .env file")
}
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
mux := http.NewServeMux()
mux.HandleFunc("/", indexHandler)
http.ListenAndServe(":"+port, mux)
}
tpl เป็นตัวแปรระดับแพ็กเกจที่ชี้ไปที่นิยามเทมเพลตจากไฟล์ที่จัดเตรียมไว้ การเรียกเพื่อ template.ParseFiles แยกวิเคราะห์ index.html ไฟล์ในรูทของไดเร็กทอรีโครงการของเราและทำการตรวจสอบความถูกต้อง
การเรียกใช้ template.ParseFiles ถูกห่อด้วย template.Must เพื่อให้ code panics หากได้รับข้อผิดพลาดขณะแยกวิเคราะห์ไฟล์เทมเพลต เหตุผลที่เรา code panics ที่นี่แทนที่จะพยายามจัดการกับข้อผิดพลาดนั้นเป็นเพราะเว็บแอปที่มีเทมเพลตที่ใช้งานไม่ได้นั้นไม่ใช่เว็บแอปมากนัก เป็นปัญหาที่ควรแก้ไขก่อนที่จะพยายามรีสตาร์ทเซิร์ฟเวอร์
ในฟังก์ชัน indexHandler เทมเพลต tpl จะดำเนินการโดยระบุอาร์กิวเมนต์สองอย่าง: ตำแหน่งที่เราต้องการเขียนเอาต์พุต และข้อมูลที่เราต้องการที่จะส่งผ่านไปยังเทมเพลต
ในกรณีข้างต้น เรากำลังเขียนผลลัพธ์ไปยังอินเทอร์เฟซ ResponseWriter และเนื่องจากเราไม่มีข้อมูลที่จะส่งไปยังเทมเพลตของเราในขณะนี้ nil จึงถูกส่งผ่านเป็นอาร์กิวเมนต์ที่สอง
หยุดกระบวนการทำงานในเทอร์มินัลของคุณโดยใช้ ctrl-c จากนั้นสร้างและเรียกใช้เว็บเซิร์ฟเวอร์อีกครั้งด้วย คำสั่ง go build และ ./news-demo-starter-files
จากนั้นรีเฟรชเบราว์เซอร์ของคุณ คุณควรเห็นข้อความ “News App Demo” บนหน้าดังที่แสดงด้านล่าง:
รีสตาร์ทเซิร์ฟเวอร์อัตโนมัติ
การสร้างและรีสตาร์ทเซิร์ฟเวอร์นั้นค่อนข้างน่าเบื่อทุกครั้งที่คุณทำการเปลี่ยนแปลงโค้ด โชคดีที่คุณสามารถหลีกเลี่ยงสิ่งนั้นได้ด้วยความช่วยเหลือของแพ็คเกจ Fresh เรียกใช้คำสั่งด้านล่างเพื่อติดตั้งแพ็คเกจในไดเร็กทอรี GOBIN ของคุณ:
go get github.com/pilu/fresh
จากนั้นรันคำสั่ง fresh ที่รูทของไดเร็กทอรีโครงการของคุณ:
fresh
จากนี้ไป Fresh จะสร้างและเรียกใช้เว็บเซิร์ฟเวอร์ของคุณทุกครั้งที่คุณสร้าง แก้ไข หรือลบไฟล์ Go หรือเทมเพลตในโปรเจ็กต์
เพิ่มแถบนำทาง
แทนที่โค้ดไฟล์ index.html ของคุณดังที่แสดงด้านล่าง:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>News App Demo</title>
<link rel="stylesheet" href="/assets/style.css" />
</head>
<body>
<main>
<header>
<a class="logo" href="/">News Demo</a>
<form action="/search" method="GET">
<input
autofocus
class="search-input"
value=""
placeholder="Enter a news topic"
type="search"
name="q"
/>
</form>
<a
href="https://github.com/freshman-tech/news"
class="button github-button"
>View on GitHub</a
>
</header>
</main>
</body>
</html>
เมื่อคุณรีเฟรชเบราว์เซอร์ คุณควรเห็นองค์ประกอบที่เพิ่มด้านบนในผลลัพธ์ที่แสดง:
ให้บริการไฟล์สแตติก
สังเกตว่าแถบนำทางที่เราเพิ่มไว้ด้านบนยังคงว่างเปล่าแม้ว่า assets/style.css ไฟล์จะเชื่อมโยงอย่างถูกต้องใน < head > เอกสารของเราก็ตาม เนื่องจากเรายังไม่ได้ลงทะเบียน /assetsรู ปแบบในมัลติเพล็กเซอร์ HTTP เราจำเป็นต้องตรวจสอบให้แน่ใจว่าคำขอทั้งหมดที่ตรงกับรูปแบบนี้ทำหน้าที่เป็นไฟล์คงที่
สิ่งแรกที่ต้องทำคือสร้างอินสแตนซ์อ็อบเจ็กต์ไฟล์เซิร์ฟเวอร์โดยส่งไดเร็กทอรีที่วางไฟล์สแตติกทั้งหมดของเรา:
fs := http.FileServer(http.Dir("assets"))
ต่อไป เราต้องบอกให้เราเตอร์ของเราใช้วัตถุเซิร์ฟเวอร์ไฟล์นี้สำหรับเส้นทางทั้งหมดที่ขึ้นต้นด้วยคำนำหน้า /assets/ :
mux.Handle("/assets/", http.StripPrefix("/assets/", fs))
http.StripPrefix() วิธีการปรับเปลี่ยนการร้องขอ URL โดยการลอกออกคำนำหน้าระบุไว้ก่อนที่จะส่งต่อการจัดการของการร้องขอไปยัง http.Handler ในพารามิเตอร์ที่สอง
ตัวอย่างเช่นถ้าทำการร้องขอสำหรับ /assets/style.css ไฟล์ StripPrefix() จะตัด /assets/ ส่วนหนึ่งและส่งคำขอแก้ไขเพื่อจัดการส่งกลับโดยดังนั้นจึงจะเห็นเป็นทรัพยากรที่ร้องขอ http.FileServer() style.css จากนั้นจะค้นหาและให้บริการทรัพยากรที่สัมพันธ์กับโฟลเดอร์ที่คุณระบุเป็นไดเรกทอรีรากสำหรับไฟล์สแตติก ( assets ในกรณีนี้)
สังเกตการใช้ Handle แทนที่ HandleFunc นี่ นั่นเป็นเพราะ http.FileServer() วิธีการส่งคืนประเภท http.Handler แทนที่จะเป็น HandlerFunc
เมื่อคุณรีเฟรชเบราว์เซอร์แล้ว ควรจะเริ่มทำงานดังสไตล์ที่แสดงด้านล่าง:
สร้าง /search เส้นทาง
มาสร้างเส้นทางที่จัดการคำขอค้นหาบทความข่าวกัน เราจะทำให้การใช้งานของข่าว API สำหรับการประมวลผลแบบสอบถามดังนั้นคุณจำเป็นต้องลงทะเบียนสำหรับ API ฟรี ได้ที่นี่ https://newsapi.org/register
เมื่อคุณมี API key ของคุณแล้ว ให้เพิ่มลงในไฟล์ .env ดังที่แสดงด้านล่าง:
PORT=3000
NEWS_API_KEY=<your api key>
กลับไปที่ไฟล์ main.go ของคุณ import แพ็กเกจ “fmt” และ “net/url” เพิ่มเข้าไป
import (
"fmt"
"html/template"
"log"
"net/http"
"net/url"
"os"
"github.com/joho/godotenv"
)
และเพิ่มฟังก์ชันใหม่ searchHandler บริเวณด้านล่างของฟังก์ชัน indexHandler :
func searchHandler(w http.ResponseWriter, r *http.Request) {
u, err := url.Parse(r.URL.String())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
params := u.Query()
searchQuery := params.Get("q")
page := params.Get("page")
if page == "" {
page = "1"
}
fmt.Println("Search Query is: ", searchQuery)
fmt.Println("Page is: ", page)
}
เส้นทางนี้ต้องการพารามิเตอร์การค้นหาสองรายการ: q แสดงถึงข้อความค้นหาของผู้ใช้ และ page ใช้เพื่อเลื่อนดูผลลัพธ์ พารามิเตอร์ page นี้เป็นทางเลือก หากยังไม่ได้รวมอยู่ใน URL1 เราก็จะคิดว่าหน้าเป็น โค้ดด้านบนจะแยก q และ พารามิเตอร์ page จาก URL คำขอและพิมพ์ทั้งสองไปยังเอาต์พุตมาตรฐาน
ลงทะเบียน ฟังก์ชัน searchHandler เป็นตัวจัดการสำหรับรูปแบบ /search ดังที่แสดงด้านล่าง:
mux.HandleFunc("/search", searchHandler)
โค้ดไฟล์ main.go ทั้งหมด ณ จุดนี้
package main
import (
"fmt"
"html/template"
"log"
"net/http"
"net/url"
"os"
"github.com/joho/godotenv"
)
var tpl = template.Must(template.ParseFiles("index.html"))
func indexHandler(w http.ResponseWriter, r *http.Request) {
tpl.Execute(w, nil)
}
func searchHandler(w http.ResponseWriter, r *http.Request) {
u, err := url.Parse(r.URL.String())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
params := u.Query()
searchQuery := params.Get("q")
page := params.Get("page")
if page == "" {
page = "1"
}
fmt.Println("Search Query is: ", searchQuery)
fmt.Println("Page is: ", page)
}
func main() {
err := godotenv.Load()
if err != nil {
log.Println("Error loading .env file")
}
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
fs := http.FileServer(http.Dir("assets"))
mux := http.NewServeMux()
mux.Handle("/assets/", http.StripPrefix("/assets/", fs))
mux.HandleFunc("/search", searchHandler)
mux.HandleFunc("/", indexHandler)
http.ListenAndServe(":"+port, mux)
}
ณ จุดนี้ คุณสามารถพิมพ์ข้อความค้นหาลงในอินพุตการค้นหา เช่น Bitcoin -> Enter
และคุณควรเห็นข้อความค้นหาถูกพิมพ์ในเทอร์มินัลในลักษณะที่แสดงด้านล่าง: