توسعه وب سرور در Golang - از ساده به پیچیده

توسعه وب سرور در Golang - از ساده به پیچیده

پنج سال پیش شروع کردم Gophish را توسعه دهید، این فرصتی را برای یادگیری Golang فراهم کرد. من متوجه شدم که Go یک زبان قدرتمند است که با کتابخانه های زیادی تکمیل شده است. Go همه کاره است: به ویژه، می توان از آن برای توسعه برنامه های سمت سرور بدون هیچ مشکلی استفاده کرد.

این مقاله در مورد نوشتن سرور در Go است. بیایید با چیزهای ساده ای مانند "Hello world!" شروع کنیم و با برنامه ای با قابلیت های زیر پایان دهیم:

- استفاده از Let's Encrypt برای HTTPS.
- کار به عنوان یک روتر API.
- کار با میان افزار
- پردازش فایل های ثابت
- خاموش شدن صحیح

Skillbox توصیه می کند: دوره عملی "توسعه دهنده پایتون از ابتدا".

یادآوری می کنیم: برای همه خوانندگان "Habr" - تخفیف 10 روبل هنگام ثبت نام در هر دوره Skillbox با استفاده از کد تبلیغاتی "Habr".

سلام دنیا!

شما می توانید یک وب سرور در Go خیلی سریع ایجاد کنید. در اینجا نمونه ای از استفاده از یک کنترل کننده است که "Hello, world!" وعده داده شده در بالا را برمی گرداند.

package main
 
import (
"fmt"
"net/http"
)
 
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
})
http.ListenAndServe(":80", nil)
}

پس از این، اگر برنامه را اجرا کنید و صفحه را باز کنید localhost را، بلافاصله متن "سلام، دنیا!" (البته اگر همه چیز درست کار کند).

بعداً چندین بار از کنترلر استفاده خواهیم کرد، اما ابتدا بیایید بفهمیم همه چیز چگونه کار می کند.

net/http

نمونه از بسته استفاده می کند net/http، این ابزار اصلی در Go برای توسعه سرورها و سرویس گیرندگان HTTP است. برای درک کد، بیایید معنی سه عنصر مهم را درک کنیم: http.Handler، http.ServeMux و http.Server.

کنترل کننده های HTTP

وقتی درخواستی را دریافت می کنیم، کنترل کننده آن را تجزیه و تحلیل می کند و یک پاسخ تولید می کند. هندلرها در Go به شرح زیر پیاده سازی می شوند:

type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
}

مثال اول از تابع کمکی http.HandleFunc استفاده می کند. تابع دیگری را می پیچد که به نوبه خود http.ResponseWriter و http.Request را به ServeHTTP می برد.

به عبارت دیگر، هندلرها در Golang به صورت یک رابط ارائه می شوند که گزینه های زیادی را در اختیار برنامه نویس قرار می دهد. بنابراین، برای مثال، میان‌افزار با استفاده از یک هندلر پیاده‌سازی می‌شود، جایی که ServeHTTP ابتدا کاری را انجام می‌دهد و سپس متد ServeHTTP یک هندلر دیگر را فراخوانی می‌کند.

همانطور که در بالا ذکر شد، کنترل کننده ها به سادگی به درخواست ها پاسخ می دهند. اما کدام کنترل کننده خاص باید در یک مقطع زمانی خاص استفاده شود؟

درخواست مسیریابی

برای انتخاب درست، از مالتی پلکسر HTTP استفاده کنید. در تعدادی از کتابخانه ها به آن muxer یا روتر می گویند، اما همه آنها یک چیز هستند. وظیفه مالتی پلکسر تجزیه و تحلیل مسیر درخواست و انتخاب کنترل کننده مناسب است.

اگر برای مسیریابی پیچیده نیاز به پشتیبانی دارید، بهتر است از کتابخانه های شخص ثالث استفاده کنید. برخی از پیشرفته ترین - گوریل/موکس и گو-چی/چی، این کتابخانه ها امکان پیاده سازی پردازش میانی را بدون مشکل فراهم می کنند. با کمک آنها، می توانید مسیریابی wildcard را پیکربندی کنید و تعدادی کار دیگر را انجام دهید. مزیت آنها سازگاری با هندلرهای استاندارد HTTP است. در نتیجه می توانید کدهای ساده ای بنویسید که در آینده قابل تغییر باشند.

کار با فریم ورک های پیچیده در شرایط عادی به راه حل های غیر استاندارد نیاز دارد و این امر استفاده از کنترل کننده های پیش فرض را به طور قابل توجهی پیچیده می کند. برای ایجاد اکثریت قریب به اتفاق برنامه ها، ترکیبی از کتابخانه پیش فرض و یک روتر ساده کافی خواهد بود.

پردازش پرس و جو

علاوه بر این، ما به مؤلفه‌ای نیاز داریم که برای اتصالات ورودی «گوش» کند و همه درخواست‌ها را به مدیریت صحیح هدایت کند. http.Server به راحتی می تواند این کار را انجام دهد.

موارد زیر نشان می دهد که سرور مسئول تمام وظایفی است که مربوط به پردازش اتصال است. این، برای مثال، با استفاده از پروتکل TLS کار می کند. برای اجرای تماس http.ListenAndServer، از یک سرور HTTP استاندارد استفاده می شود.

حالا بیایید به مثال های پیچیده تر نگاه کنیم.

اضافه کردن Let's Encrypt

به طور پیش فرض، برنامه ما از طریق پروتکل HTTP اجرا می شود، اما توصیه می شود از پروتکل HTTPS استفاده کنید. این را می توان بدون مشکل در Go انجام داد. اگر گواهینامه و کلید خصوصی دریافت کرده اید، کافی است ListenAndServeTLS را با گواهینامه و فایل های کلیدی صحیح ثبت کنید.

http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)

شما همیشه می توانید بهتر عمل کنید.

بیایید رمزگذاری کنیم گواهینامه های رایگان با تمدید خودکار ارائه می دهد. برای استفاده از این سرویس به یک بسته نیاز دارید autocert.

ساده ترین راه برای پیکربندی آن استفاده از متد autocert.NewListener در ترکیب با http.Serve است. این روش به شما اجازه می‌دهد تا گواهی‌های TLS را در حالی که سرور HTTP درخواست‌ها را پردازش می‌کند، دریافت و به‌روزرسانی کنید:

http.Serve(autocert.NewListener("example.com"), nil)

اگر در مرورگر باز کنیم example.com، یک پاسخ HTTPS "سلام، دنیا!"

اگر به پیکربندی دقیق تری نیاز دارید، باید از مدیر autocert.Manager استفاده کنید. سپس نمونه http.Server خود را ایجاد می کنیم (تا کنون به طور پیش فرض از آن استفاده می کردیم) و مدیر را به سرور TLSconfig اضافه می کنیم:

m := &autocert.Manager{
Cache:      autocert.DirCache("golang-autocert"),
Prompt:     autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("example.org", "www.example.org"),
}
server := &http.Server{
    Addr:      ":443",
    TLSConfig: m.TLSConfig(),
}
server.ListenAndServeTLS("", "")

این یک راه آسان برای اجرای پشتیبانی کامل HTTPS با تمدید خودکار گواهی است.

اضافه کردن مسیرهای سفارشی

روتر پیش فرض موجود در کتابخانه استاندارد خوب است، اما بسیار ابتدایی است. بیشتر برنامه‌ها به مسیریابی پیچیده‌تر، از جمله مسیرهای تودرتو و عام، یا رویه‌ای برای تنظیم الگوها و پارامترهای مسیر نیاز دارند.

در این مورد ارزش استفاده از بسته ها را دارد گوریل/موکس и گو-چی/چی. نحوه کار با دومی را یاد خواهیم گرفت - یک مثال در زیر نشان داده شده است.

فایل api/v1/api.go حاوی مسیرهایی برای API ما است:

/ HelloResponse is the JSON representation for a customized message
type HelloResponse struct {
Message string `json:"message"`
}
 
// HelloName returns a personalized JSON message
func HelloName(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
response := HelloResponse{
Message: fmt.Sprintf("Hello %s!", name),
}
jsonResponse(w, response, http.StatusOK)
}
 
// NewRouter returns an HTTP handler that implements the routes for the API
func NewRouter() http.Handler {
r := chi.NewRouter()
r.Get("/{name}", HelloName)
return r
}

پیشوند api/vq را برای مسیرها در فایل اصلی قرار می دهیم.

سپس می‌توانیم آن را به روتر اصلی خود تحت پیشوند api/v1/ در برنامه اصلی خود نصب کنیم:

// NewRouter returns a new HTTP handler that implements the main server routes
func NewRouter() http.Handler {
router := chi.NewRouter()
    router.Mount("/api/v1/", v1.NewRouter())
    return router
}
http.Serve(autocert.NewListener("example.com"), NewRouter())

سهولت کار Go با مسیرهای پیچیده، ساده سازی ساختار و نگهداری برنامه های کاربردی بزرگ و پیچیده را ممکن می سازد.

کار با میان افزار

مرحله بندی شامل پیچیدن یک کنترل کننده HTTP با دیگری است که امکان انجام سریع احراز هویت، فشرده سازی، ثبت و چندین عملکرد دیگر را فراهم می کند.

به عنوان مثال، اجازه دهید به رابط http.Handler نگاهی بیندازیم؛ ما از آن برای نوشتن یک handler که کاربران سرویس را احراز هویت می کند، استفاده می کنیم.

func RequireAuthentication(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !isAuthenticated(r) {
            http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
            return
        }
        // Assuming authentication passed, run the original handler
        next.ServeHTTP(w, r)
    })
}

روترهای شخص ثالث مانند chi وجود دارند که به شما امکان می دهند عملکرد میان افزار را گسترش دهید.

کار با فایل های ثابت

کتابخانه استاندارد Go شامل قابلیت هایی برای کار با محتوای ثابت، از جمله تصاویر، جاوا اسکریپت و فایل های CSS است. از طریق تابع http.FileServer می توان به آنها دسترسی داشت. کنترل کننده ای را برمی گرداند که فایل ها را از یک دایرکتوری خاص ارائه می کند.

func NewRouter() http.Handler {
    router := chi.NewRouter()
    r.Get("/{name}", HelloName)
 
// Настройка раздачи статических файлов
staticPath, _ := filepath.Abs("../../static/")
fs := http.FileServer(http.Dir(staticPath))
    router.Handle("/*", fs)
    
    return r

قطعاً شایان ذکر است که http.Dir محتویات دایرکتوری را در صورتی که حاوی فایل اصلی index.html نباشد نمایش می دهد. در این حالت، برای جلوگیری از به خطر افتادن دایرکتوری، باید از بسته استفاده کنید unindexed.

خاموش شدن صحیح

Go همچنین دارای یک ویژگی به نام خاموش کردن برازنده سرور HTTP است. این کار را می توان با استفاده از متد Shutdown() انجام داد. سرور به صورت گوروتین راه اندازی می شود و سپس کانال برای دریافت سیگنال وقفه گوش داده می شود. به محض دریافت سیگنال، سرور خاموش می شود، اما نه بلافاصله، بلکه پس از چند ثانیه.

handler := server.NewRouter()
srv := &http.Server{
    Handler: handler,
}
 
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
 
// Wait for an interrupt
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
 
// Attempt a graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)

به عنوان یک نتیجه گیری

Go یک زبان قدرتمند با یک کتابخانه استاندارد تقریبا جهانی است. قابلیت های پیش فرض آن بسیار گسترده است و می توان آنها را با استفاده از رابط ها افزایش داد - این به شما امکان می دهد سرورهای HTTP واقعا قابل اعتمادی را توسعه دهید.

Skillbox توصیه می کند:

منبع: www.habr.com

اضافه کردن نظر