הממשק http.Handler
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 77 מתוך 107.
הממשק http.Handler הוא הבסיס לארכיטקטורת שרת האינטרנט של Go. הוא מגדיר כיצד בקשות HTTP מעובדות, וכל טיפוס שמממש אותו יכול לשרת בקשות אינטרנט.
הממשק פשוט להפליא:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}כל טיפוס עם מתודה ServeHTTP יכול לטפל בבקשות HTTP. הנה מטפל מותאם אישית:
type GreetHandler struct {
Greeting string
}
func (h GreetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s, visitor!", h.Greeting)
}אפשר להשתמש במטפל הזה ישירות עם שרת:
handler := GreetHandler{Greeting: "Welcome"}
http.Handle("/greet", handler)למקרים פשוטים יותר, Go מספקת את http.HandlerFunc, טיפוס שמאפשר לך להמיר פונקציה רגילה למטפל:
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello!")
}
http.Handle("/hello", http.HandlerFunc(hello))העוצמה של הממשק הזה מתבהרת כשמבינים שמטפלים יכולים לעטוף מטפלים אחרים. כך מתאפשרים דפוסי תווכה שבהם מוסיפים רישום ביומן, אימות או היבטים רוחביים אחרים בלי לשנות את הלוגיקה המקורית של המטפל.
אתגר
קלבואו נבנה מערכת פשוטה של מטפלי HTTP שמדמה את אופן העיבוד של בקשות אינטרנט! מכיוון שאי אפשר להריץ שרת HTTP אמיתי בסביבה הזו, תיצרו מטפלים שמממשים את הממשק http.Handler ותדגימו איך הם מעבדים בקשות באמצעות אובייקטים מדומים של בקשה/תגובה.
תארגנו את הקוד בשני קבצים:
handlers.go: הגדירו את מטפלי ה-HTTP המותאמים אישית שלכם.צרו מבנה
MockResponseWriterשמתעד מה היה נכתב לתגובת HTTP. עליו לשמור את גוף התגובה כמחרוזת ולממש מתודהWrite(data []byte) (int, error)שמוסיפה נתונים לגוף. הוסיפו מתודהBody() stringכדי לקבל את התגובה המצטברת.צרו מבנה
MockRequestעם שדות מחרוזתPathו-Methodכדי לדמות בקשת HTTP.כעת צרו שני מבני מטפל שמממשים את תבנית הממשק
http.Handler(עם מתודהServeHTTPשמקבלת*MockResponseWriterו-*MockRequest):WelcomeHandlerעם שדהMessage— כותב את ההודעה ולאחריה את נתיב הבקשהMethodHandler— כותב תגובות שונות בהתאם לשיטת הבקשה:"Fetching data"עבור GET,"Creating resource"עבור POST, ו-"Method not supported"עבור כל דבר אחר
main.go: עבדו בקשות באמצעות המטפלים שלכם.קראו סוג מטפל (
welcomeאוmethod), ואז קראו את פרטי הבקשה. עבור מטפל הברכה, קראו גם את הודעת הברכה.צרו את המטפל המתאים, בנו בקשה מדומה וקראו ל-
ServeHTTPכדי לעבד אותה. הדפיסו את גוף התגובה.
הקלטים הבאים יסופקו:
- שורה 1: סוג המטפל (
welcomeאוmethod) - שורה 2: נתיב הבקשה
- שורה 3: שיטת הבקשה
- שורה 4 (רק עבור welcome): הודעת הברכה
לדוגמה, עבור הקלט:
welcome
/home
GET
Hello fromהפלט שלכם צריך להיות:
Hello from /homeועבור הקלט:
method
/api/users
POSTהפלט שלכם צריך להיות:
Creating resourceועבור הקלט:
method
/api/data
GETהפלט שלכם צריך להיות:
Fetching dataועבור הקלט:
method
/api/items
DELETEהפלט שלכם צריך להיות:
Method not supportedהאתגר הזה מדגים את הרעיון המרכזי של הממשק http.Handler: כל טיפוס שיש לו מתודה ServeHTTP יכול לטפל בבקשות. ביישומים אמיתיים, http.ResponseWriter ו-*http.Request מגיעים מספריית התקן של Go, אבל דפוס המימוש של המטפלים נשאר בדיוק אותו הדבר.
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// קרא את סוג המטפל
scanner.Scan()
handlerType := scanner.Text()
// קרא את נתיב הבקשה
scanner.Scan()
path := scanner.Text()
// קרא את שיטת הבקשה
scanner.Scan()
method := scanner.Text()
// צור בקשה מדומה
request := &MockRequest{
Path: path,
Method: method,
}
// צור כותב תגובה מדומה
response := &MockResponseWriter{}
// TODO: על סמך handlerType, צור את המטפל המתאים
// אם handlerType הוא "welcome", קרא את הודעת הפתיחה וצור WelcomeHandler
// אם handlerType הוא "method", צור MethodHandler
// קרא ל-ServeHTTP של המטפל עם response ו-request
if handlerType == "welcome" {
scanner.Scan()
message := scanner.Text()
// TODO: צור WelcomeHandler עם ההודעה וקרא ל-ServeHTTP
_ = message // הסר שורה זו בעת המימוש
} else if handlerType == "method" {
// TODO: צור MethodHandler וקרא ל-ServeHTTP
}
// הדפס את גוף התגובה
fmt.Println(response.Body())
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP ב-Go
קבצים חיצונייםסביבת העבודה והמודולים של Goחבילות וייבואשמות מיוצאים לעומת שמות לא מיוצאיםמבוא ל-OOP ב-GoStructs כמחלקותהגדרת מתודות עבור Structsמקבלי Pointer לעומת Valueאתחול Structפונקציות Constructorחזרה – מחשבון פשוט4ממשקים
מבוא לממשקיםמימוש משתמעממשק כחוזהממשק ריק (any)אימות טיפוסמתג טיפוסיםהרכבת ממשקיםממשקי Stringer ו-Errorסיכום – מחשבון צורות7כימוס
שדות מיוצאים לעומת שדות לא מיוצאיםכימוס ברמת החבילהמתודות Getter ו-Setterהסתרת מידע ב-Goסיכום – רשומות תלמידים10Generics (Go 1.18 ומעלה)
מבוא ל־Genericsפרמטרים של טיפוסיםאילוצי טיפוסיםמבנים גנרייםפתרון עוקף למתודות גנריותסיכום – אוסף גנרי2העמקה בסוגים וב-Structs
סוגים בסיסיים ומורכביםהגדרות של סוגים מותאמים אישיתתגיות StructStructs אנונימייםStructs מקונניםערכי אפס וברירות מחדלסיכום – ספר אנשי קשר5קומפוזיציה במקום ירושה
למה ב-Go אין ירושהיסודות הטמעת Structקידום מתודותהטמעת כמה Structsהטמעה לעומת אגרגציההסתרת מתודות מוטמעותסיכום – היררכיית עובדים8טיפול בשגיאות ו-OOP
ממשק errorסוגי שגיאות מותאמים אישיתעטיפת שגיאות (fmt.Errorf)שגיאות Sentinelerrors.Is() ו-errors.As()Panic, Defer ו-Recoverסיכום – מנתח קבצים11ספרייה סטנדרטית ותכנות מונחה עצמים
io.Reader ו-io.Writersort.Interfaceהממשק fmt.Stringerencoding/json עם מבניםהממשק http.Handlerסיכום – מודלים של REST API3מצביעים וזיכרון
יסודות המצביעים ב-Goמצביעים למבניםהעברה לפי ערך לעומת לפי הפניההפונקציה new()איסוף אשפה ב-Goסיכום - בונה רשימה מקושרת6פולימורפיזם ב-Go
פולימורפיזם באמצעות ממשקיםטיפוס ברווז ב-Goכללי התאמה לממשקאוספים פולימורפייםהזרקת תלויותסיכום - מעבד תשלומים9מקביליות ו-OOP
יסודות GoroutinesChannels ותקשורתChannel עם חוצץ לעומת ללא חוצץפקודת Selectsync.Mutex ו-sync.RWMutexsync.WaitGroupתכנון Struct בטוח לשימוש במקבילסיכום - מאגר עובדים12מושגים מתקדמים בתכנות מונחה עצמים
דפוס האפשרויות הפונקציונליותדפוס Builder ב-Goשרשור מתודותכינויי טיפוסים לעומת הגדרותיסודות Reflectionסקירה כללית של יצירת קודתרגלו בעצמכם: קומפיילר Go אונליין