วิธีสร้างเว็บแอปพลิเคชันแรกของคุณด้วย Go


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

และคุณควรเห็นข้อความค้นหาถูกพิมพ์ในเทอร์มินัลในลักษณะที่แสดงด้านล่าง:

Leave a Reply

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