Webová služba v Go je nejjednodušší věc, jakou v této kategorii můžeš provozovat, a když se chová špatně, má nejméně výmluv. Standardní knihovna už obsahuje HTTP server pro produkci, výstupem je jediný staticky slinkovaný binární soubor a nečinná služba se vejde do několika desítek megabajtů. Chyba je téměř vždy v jedné z pěti drobností: služba naslouchá na 127.0.0.1, má napevno zadaný port, ignoruje SIGTERM, nemá žádné timeouty, nebo se garbage collectoru dovolí růst přes limit paměti kontejneru.
Tento článek pokrývá všech pět, a navíc to, co je specifické pro nasazení na aplikační server, který tvůj kód kompiluje sám: co musí říkat go.mod, co se stane při každém startu, jak do toho zapadá deploy z GitHubu, co proxy posílá tvým handlerům, na jaké databáze se napojit a co číst, když konzole ukáže chybu.
Co Go služba potřebuje za běhu#
Skoro nic, v tom je její kouzlo. go build slinkuje tvůj kód, závislosti a runtime Go do jednoho spustitelného souboru. Na serveru není žádný interpret, žádné virtuální prostředí ani node_modules. Runtime si nese vlastní plánovač a garbage collector, takže proces v Go je jeden binární soubor a sada proměnných prostředí.
Dva detaily stojí za to znát dřív, než tě překvapí:
- Cgo mění situaci. S
CGO_ENABLED=1(výchozí, pokud je přítomen překladač C) se může binárka dynamicky linkovat proti glibc a balíčky jakomattn/go-sqlite3potřebují při buildu toolchain pro C. SCGO_ENABLED=0je binárka plně statická a takovou závislost nemá. Pokud knihovna, kterou používáš, má alternativu v čistém Go -modernc.org/sqlitemístogo-sqlite3,pgxmísto čehokoli založeného na libpq - dej jí na serveru, který neovládáš, přednost. - Časová pásma a certifikáty. V minimálním prostředí může chybět databáze časových pásem i balík CA certifikátů. Pokud tvůj program volá
time.LoadLocation("Europe/Berlin")nebo posílá odchozí HTTPS požadavky, importujtime/tzdata, aby se databáze pásem zabudovala do binárky. Odchozí TLS potřebuje kořenové certifikáty ze systému, takže při prvním deployi ověř, že odchozí volání funguje, a nespoléhej se na to.
Paměť je číslo, kolem kterého plánuj. Malá služba na net/http s poolem databázových spojení běžně v klidu zabírá 10 až 30 MB rezidentní paměti a roste s počtem souběžných požadavků a buffery, ne s velikostí kódu. Proto je nejmenší aplikační tarif rozumným domovem pro většinu služeb v Go a proto rezervu občas potřebuje spíš build než samotný běh.
net/http, chi nebo gin#
Začni standardní knihovnou. Od Go 1.22 rozumí výchozí ServeMux metodám a zástupným znakům v cestě, čímž zmizel hlavní důvod, proč lidé vůbec sahali po routeru:
mux := http.NewServeMux()mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok"))})mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") fmt.Fprintf(w, "user %s\n", id)})Chi použij, když chceš skládat middleware a skupiny rout při zachování standardního rozhraní http.Handler. Vše napsané pro net/http funguje pod chi beze změny, takže přechodem později nic neztratíš:
r := chi.NewRouter()r.Use(middleware.RequestID, middleware.Recoverer)r.Route("/api", func(r chi.Router) { r.Get("/health", health) r.Get("/users/{id}", getUser)})Gin použij, když chceš jeho pomocníky pro binding a validaci a ekosystém hotových middlewarů. Má vlastní typ kontextu, takže handlery jsou na gin navázané. V produkci nastav v prostředí GIN_MODE=release; výchozí debug režim při startu vypisuje každou routu a loguje víc, než chceš:
GIN_MODE=releaseŽádný ze tří není rychlejší způsobem, na kterém na malém tarifu záleží. Rozdíl mezi nimi je menší než jeden round trip do databáze. Vyber podle toho, jak chceš psát handlery a middleware, a ušetřené úsilí věnuj timeoutům a vypínání, což za tebe nevyřeší žádný router.
go.mod, go.sum a build při startu#
Na řadě Go u RE:NODE se server buduje pomocí go build při každém startu. Ta jedna věta má důsledky pro to, jak projekt uspořádáš, takže stojí za to postupovat uvážlivě.
Za prvé, go.mod není dekorace. Pojmenovává modul, určuje minimální verzi Go a vypisuje každou závislost s verzí:
module github.com/yourname/apigo 1.23require ( github.com/go-chi/chi/v5 v5.1.0 github.com/jackc/pgx/v5 v5.7.1)Commitni go.mod i go.sum. Druhý soubor obsahuje kontrolní součty každé verze modulu a bez něj musí build všechno zjišťovat a ověřovat od nuly. Před každým commitem spusť go mod tidy, aby se nepoužívané požadavky odstranily a chybějící doplnily; go.mod, který nesouhlasí s tvými importy, je nejčastější důvod, proč build funguje na tvém notebooku, ale na serveru selže.
Za druhé, na řádku go záleží. Pokud uvádí verzi novější, než je toolchain na serveru, může se nástroj Go pokusit stáhnout novější toolchain, nebo odmítnout build, podle direktivy toolchain a nastavení GOTOOLCHAIN. Nenastavuj go 1.25 jen proto, že to máš zrovna nainstalované; nastav nejnižší verzi, kterou tvůj kód skutečně potřebuje. Než se spolehneš na novou vlastnost jazyka, zkontroluj verzi Go, kterou řada nabízí.
Za třetí, každý start znamená kompilaci. To je výhoda - binárka vždy odpovídá zdrojovému kódu na serveru - i cena, protože restart není okamžitý. Malá služba se při teplé cache buildu přeloží během několika sekund, při studené za minutu i déle. Počítej s restartem jako s deployem: trvá tak dlouho jako build plus doba, kterou tvá služba potřebuje k otevření spojení.
Za čtvrté, chyby kompilace se stávají chybami startu. Pokud se kód nepřeloží, server nenastartuje a hlášení překladače je v konzoli. To je lepší selhání než napůl fungující služba, ale znamená to, že rozbitý commit na větvi, kterou server sleduje, službu shodí. Před pushem spusť lokálně tohle:
$ go mod tidy$ go vet ./...$ go test ./...$ go build -o /tmp/api .Balíček main nech tam, kde ho řada očekává. Nejjednodušší uspořádání je main.go v kořeni repozitáře; pokud dáváš přednost cmd/api/main.go, zkontroluj na záložce Startup, co řada Go buildí, a před prvním deployem se ujisti, že míří na tvůj balíček main.
Porty, bind adresa a proměnné prostředí#
Jsou tu dvě nastavení a obě mají uvnitř kontejneru špatné výchozí hodnoty.
První je bind adresa. localhost:8080 a 127.0.0.1:8080 naslouchají jen na loopback rozhraní, na které zvenku z kontejneru nikdo nedosáhne. Použij 0.0.0.0, nebo nech hostitele prázdného (":8080"), což naslouchá na všech rozhraních.
Druhé je port. Nezadávej ho napevno. Přidělený port přichází jako proměnná prostředí a záložka Startup v panelu ukazuje proměnné, které tvůj server má. Přečti si název, který tam vidíš; příklad níže zkusí nejdřív PORT, potom SERVER_PORT a nakonec vývojovou výchozí hodnotu, takže stejná binárka běží na tvém notebooku i na serveru:
func port() string { for _, name := range []string{"PORT", "SERVER_PORT"} { if v := os.Getenv(name); v != "" { return v } } return "8080"}func requireEnv(name string) string { v := os.Getenv(name) if v == "" { log.Fatalf("missing required environment variable %s", name) } return v}Každou proměnnou přečti jednou při startu a hned skonči, pokud chybí nějaká povinná. Služba, která zjistí chybějící DATABASE_URL ve tři ráno na vzácné cestě kódem, se ladí hůř než ta, která odmítne nastartovat. Na záložce Startup také přidáváš vlastní proměnné - URL databáze, tajný klíč relací, API klíče - takže zůstávají mimo repozitář. Proměnné prostředí a tajné údaje popisuje, co tam patří a co ne.
Krátká struktura s konfigurací drží pořádek a dává ti jedno místo, kde při startu zalogovat platná nastavení (bez tajných údajů):
type Config struct { Addr string DatabaseURL string Env string}func load() Config { return Config{ Addr: ":" + port(), DatabaseURL: requireEnv("DATABASE_URL"), Env: os.Getenv("APP_ENV"), }}Timeouty, logování a graceful shutdown#
Nulová hodnota http.Server nemá žádné timeouty. Na notebooku to nevadí, v produkci je to problém, protože klient, který otevře spojení a nic nepošle, drží goroutinu a souborový deskriptor donekonečna. Použij explicitní server:
srv := &http.Server{ Addr: cfg.Addr, Handler: mux, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 15 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, MaxHeaderBytes: 1 << 20,}| Pole | Co omezuje | Typická hodnota |
|---|---|---|
ReadHeaderTimeout | Čas na přečtení hlaviček požadavku | 5 s |
ReadTimeout | Celý požadavek včetně těla | 15 s |
WriteTimeout | Čas na zapsání odpovědi | 30 s |
IdleTimeout | Keep-alive spojení mezi požadavky | 60 s |
WriteTimeout je ten, který kousne. Pokrývá celou odpověď, takže streamovací endpoint, velké stahování nebo stream server-sent events se po jeho vypršení utne. U takových rout zruš deadline pro daný požadavek pomocí http.NewResponseController(w).SetWriteDeadline(time.Time{}), nebo je provozuj na samostatném serveru s jinými limity.
Loguj na standardní výstup, aby to měla konzole v panelu. Od Go 1.21 má standardní knihovna strukturované logování v log/slog:
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))slog.SetDefault(logger)slog.Info("listening", "addr", cfg.Addr)Řádky JSON se dobře prohledávají, ale špatně čtou naživo, takže když APP_ENV není production, přepni na slog.NewTextHandler.
Teď vypínání. Zastavení nebo restart serveru pošle SIGTERM. Pokud ho tvůj program neošetří, proces je po uplynutí odkladu zabit a vše, co bylo rozpracované, se zahodí - napůl zapsané odpovědi, bufferované zápisy, běžící úlohy. V Go je jeho ošetření krátké:
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)defer stop()go func() { if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatal(err) }}()<-ctx.Done()slog.Info("shutting down")shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)defer cancel()if err := srv.Shutdown(shutdownCtx); err != nil { slog.Error("shutdown", "err", err)}db.Close()Shutdown přestane přijímat nová spojení, počká na dokončení aktivních požadavků a vrátí se, jakmile skončí, nebo když vyprší kontext. Dvě pasti: ListenAndServe se při zavolání Shutdown okamžitě vrátí s http.ErrServerClosed, což není chyba a nesmí se tak logovat; a Shutdown nečeká na hijacknutá spojení, například websockety, takže je zavři sám. Timeout vypínání drž kratší, než kolik ti dává platforma, přičemž deset sekund je rozumný strop. Graceful shutdown a health checky pokrývá stranu health endpointu, což je v Go handler vracející konstantu.
Deploy z GitHubu#
Na aplikačních řadách přicházejí deploye jen z GitHubu, přes GitHub App s krátkodobými tokeny, takže fungují i soukromé repozitáře a nemusíš nikam vkládat osobní přístupový token. Jsou tu dva přepínače a jsou na sobě nezávislé:
- Pull při každém startu. Pokaždé, když server nastartuje, stáhne poslední commit větve, kterou sleduje, a pak ho zbuilduje.
- Deploy při pushi. Push do větve restartuje běžící server a spustí zastavený. Každý deploy se zaznamená jako jeden záznam.
Spojíš-li to s buildem při startu, je deploy pipeline krátká:
Dva zvyky to drží v bezpečí. Deployuj z větve, která přijímá jen kód, jenž už prošel testy, protože deploy při pushi restartuje produkci s čímkoli, co tam přistane. A drž tajné údaje mimo repozitář, protože build čte stejný strom, který jsi pushnul. Pokud se push nezbuilduje, výstup překladače je v konzoli a oprava je další push.
Když poprvé pushuješ na prázdný server, pamatuj, že každá řada kromě Node.js na své adrese zobrazuje malou funkční stránku, dokud nenasadíš kód, a ta stránka nikdy nepřepíše tvé soubory. Pokud ji po deployi vidíš, deploy se neprojevil: než začneš obviňovat proxy, podívej se na záznam deploye a do konzole.
Reverse proxy, doména a X-Forwarded-For#
Ukončit TLS přímo v programu v Go jde přes ListenAndServeTLS nebo autocert a na hostingovém tarifu je to obvykle špatná volba: bereš na sebe obnovu certifikátu, přesměrování z HTTP na HTTPS a reload při změně certifikátu. Služ prosté HTTP na přiděleném portu a zbytek nech na proxy.
Aplikační tarify zahrnují slot reverzní proxy. Nasměruj záznam A na adresu zobrazenou u slotu a certifikát se vystaví a obnovuje automaticky, v rámci 21denního okna. Před testem dej záznamu čas na propagaci; certifikát nelze vystavit pro jméno, které se ještě nepřekládá na tu adresu. Připojení domény k herním serverům vede samotným záznamem a stejné kroky platí pro aplikaci. Co dělá reverse proxy vysvětluje řetězec hlaviček podrobněji.
Co to znamená pro tvé handlery:
- `r.RemoteAddr` je proxy, ne návštěvník. Skutečná adresa klienta přichází v hlavičce
X-Forwarded-For. Pokud loguešr.RemoteAddrnebo podle něj omezuješ rychlost, logueš a omezuješ proxy a všichni návštěvníci sdílejí jeden bucket. - Vezmi správný prvek.
X-Forwarded-Forje seznam oddělený čárkami. Cokoli, co klient poslal sám, je vlevo; adresa, kterou přidala tvá proxy, je vpravo. Důvěřovat hodnotě zleva umožňuje každému klientovi tvrdit, že je kdokoli. Protože proxy je tu jediná cesta dovnitř, je důvěra v hlavičku jako takovou rozumná, ale vezmi poslední záznam, který proxy připojila, ne první. - Middleware použij jednou a brzy. Chi obsahuje
middleware.RealIP, který přepisujer.RemoteAddrpodle přeposílacích hlaviček. Nainstaluj ho jen tehdy, když je zaručeno, že proxy je vždy před tebou, protože uvěří čemukoli, co hlavičky tvrdí. - Absolutní URL skládej z `X-Forwarded-Proto`. Přesměrování, odkazy v e-mailech a OAuth callbacky potřebují veřejné schéma. Kontrola
r.TLS == nilza proxy vždy říká prosté HTTP a výsledkem je smyčka přesměrování nahttp://.
func clientIP(r *http.Request) string { if xff := r.Header.Get("X-Forwarded-For"); xff != "" { parts := strings.Split(xff, ",") return strings.TrimSpace(parts[len(parts)-1]) } host, _, _ := net.SplitHostPort(r.RemoteAddr) return host}Websockety přes proxy fungují, ale potřebují heartbeaty kratší než jakýkoli idle timeout; podrobnosti jsou v článku websockety za reverse proxy a platí beze změny pro gorilla/websocket i nhooyr.io/websocket.
Databáze: Postgres, MySQL a Valkey#
Samotná služba v Go je bezstavová, což je přesně to, co chceš. Stav patří do databáze a existují dva způsoby, jak ji získat.
Každý aplikační tarif přichází se sloty pro databáze, vytvářenými v panelu s vygenerovaným hostem, uživatelem a heslem. Pro něco většího nebo samostatného jsou tu tarify hostingu databází pro PostgreSQL, MySQL 8.4 LTS, MongoDB, SQL Server Express a Valkey. K těm se připojuješ na vlastním hostu a portu tarifu s přihlašovacími údaji generovanými pro každý server. Výsledný connection string dej do proměnné prostředí na záložce Startup, nikdy ne do kódu.
Pro PostgreSQL je ovladačem pgx, který použiješ přímo nebo přes database/sql. Jeho pool je pgxpool:
pool, err := pgxpool.New(ctx, os.Getenv("DATABASE_URL"))if err != nil { log.Fatal(err)}defer pool.Close()Pro MySQL je standardní volbou go-sql-driver/mysql pod database/sql. Do DSN dej parseTime=true, jinak místo hodnot time.Time dostaneš pole bajtů, a výslovně nastav znakovou sadu:
user:pass@tcp(host:3306)/appdb?parseTime=true&charset=utf8mb4&loc=UTCdatabase/sql ve výchozím stavu pooluje a bez jakýchkoli limitů, což je část, kterou je třeba opravit. Když ji necháš být, otevře tolik spojení, kolik jich souběžné dotazy požadují, a špička v provozu se změní v chybu překročení limitu spojení na straně databáze:
db.SetMaxOpenConns(10)db.SetMaxIdleConns(5)db.SetConnMaxLifetime(30 * time.Minute)db.SetConnMaxIdleTime(5 * time.Minute)Proces v Go má jeden pool, ne jeden na workera, takže deset je štědrých pro službu na jednom či dvou jádrech. Connection pooly a limity obsahuje výpočty pro stranu databáze.
Pro cache, relace a fronty použij Valkey. Je to open-source fork Redisu a mluví protokolem Redis, takže se k němu go-redis nebo rueidis připojí beze změny. Instance je chráněná heslem a ukládá na disk, a to pomocí AOF i snapshotů. Valkey: první kroky pokrývá připojení a fronty úloh s Valkey je přirozeným dalším krokem.
Migrace spouštěj záměrně. Aplikovat je při každém startu z main je pohodlné a nebezpečné, protože smyčka restartů zopakuje napůl aplikovanou migraci. Pro cokoli nad rámec hračky je bezpečnější samostatný příkaz, nebo nástroj jako golang-migrate či goose spuštěný ručně.
Paměť: GOMEMLIMIT, GOGC a paměť při buildu#
Při dosažení limitu paměti jádro kontejner zastaví a ten se čistě restartuje, místo aby swapoval. Pro službu v Go je to hlavní provozní riziko, protože garbage collector Go ve výchozím stavu o tvém limitu nic neví. S GOGC=100 nechá haldu narůst zhruba na dvojnásobek živých dat, než začne uvolňovat, takže služba s 300 MB živých dat může dosáhnout 600 MB, aniž by bylo cokoli špatně.
Opravují to dvě nastavení, která jsou proměnnými prostředí na záložce Startup:
| Proměnná | Výchozí | Co dělá |
|---|---|---|
GOGC | 100 | Růst haldy před uvolňováním, v procentech |
GOMEMLIMIT | none | Měkký strop celkové paměti runtime Go |
GOMAXPROCS | logické CPU | Vlákna, která spouštějí kód Go současně |
GOMEMLIMIT bere velikost s příponou jednotky, například GOMEMLIMIT=800MiB. Je to měkký limit: jakmile se mu runtime blíží, collector pracuje usilovněji, aby se pod něj vešel, a vyměňuje CPU za paměť. Nastav ho pod limit kontejneru, ne na něj, protože halda Go není jediná věc, která paměť spotřebovává - počítají se i zásobníky, samotný runtime a alokace z cgo. Rozumný začátek je zhruba o 10 až 20 procent méně, než je paměť tarifu. Na tarifu s 1 GB to je GOMEMLIMIT=800MiB.
Měkký limit není lék na únik paměti. Pokud živá data limit opravdu překračují, collector běží téměř nepřetržitě, CPU stoupá, latence roste a místo čistého selhání dostaneš spirálu smrti. Pokud graf v konzoli ukazuje stálý růst paměti poté, co se provoz ustálil, najdi únik (neomezená mapa, goroutina na požadavek, která nikdy neskončí, tělo odpovědi, které se nikdy nezavřelo), místo abys zvyšoval limit.
GOGC s tím jde ruku v ruce. Snížení na 50 uvolňuje častěji a spotřebuje méně paměti za cenu CPU. Zvýšení na 200 dělá opak. S nastaveným GOMEMLIMIT můžeš dokonce dát GOGC=off a nechat uvolňování řídit jen limit, ale to je pro služby, jejichž živá halda je malá a dobře známá. Dokud ti graf nedá důvod, nech GOGC na výchozí hodnotě.
CPU je tvrdě omezené na podíl, který sis koupil: tarif s 0,5 vCPU spouští tvá vlákna touto rychlostí bez ohledu na to, co si myslí runtime. Před Go 1.25 se GOMAXPROCS ve výchozím stavu rovnal počtu logických CPU stroje, ne kvótě, což může znamenat víc vláken plánovače, než kolik kontejner využije, a zasekávání při omezení; pozdější verze berou kvótu kontejneru v úvahu. Pokud buildíš starším Go, nastav GOMAXPROCS na celé číslo jader, které ti tarif dává, nebo importuj go.uber.org/automaxprocs.
Paměť při buildu je samostatná otázka. Překladač Go je mnohem lehčí než release build v Rustu, ale není zadarmo a build probíhá uvnitř stejného kontejneru při každém startu. Čísla ze zkušenosti, ne sliby:
| Projekt | Studený build | Špička paměti při buildu |
|---|---|---|
| Malé API, tucet závislostí | 20-60 sekund | 300-500 MB |
| Typická služba, pár desítek modulů | 1-3 minuty | 500-900 MB |
| Velký strom, generovaný kód, velké protobufy | 3-8 minut | 1 GB nebo víc |
Na nejmenším tarifu se typická služba zbuilduje, ale velký strom závislostí může narazit na limit a být zastaven uprostřed kompilace, což se projeví jako restart, ne jako pomalý build. Pokud se to stane, go build -p 1 (nebo GOFLAGS=-p=1) kompiluje po jednom balíčku a snižuje špičku paměti za cenu času. Dál omez, co importuješ: závislost, která kvůli jediné funkci natáhne SDK cloudu, bývá obvyklým viníkem. U release buildů -ldflags="-s -w" odstraní symboly a ladicí informace, čímž se binárka zmenší o čtvrtinu i víc, aniž by se změnila paměť za běhu.
Co se může pokazit#
Stránka se na notebooku načte a na serveru vyprší čas. Služba naslouchá na localhost. Naslouchej na :PORT nebo 0.0.0.0:PORT.
`listen tcp :8080: bind: address already in use`. Buď běží druhá kopie tvého programu, nebo jsi napevno zadal port, který není ten přidělený. Přečti proměnnou prostředí.
Server se nikdy nedostane za první start a konzole ukazuje chybu překladače. Build selhal. Zreprodukuj to lokálně příkazem go build ./... na čistém checkoutu; obvyklými příčinami jsou chybějící záznam v go.sum, nepoužitý import nebo necommitnutý soubor.
`go: updates to go.mod needed; to update it: go mod tidy`. Tvé importy a go.mod si neodpovídají. Spusť lokálně go mod tidy a commitni oba soubory.
`go.mod requires go >= 1.24 (running go 1.23)`. Řádek go je novější než toolchain. Sniž ho na verzi, kterou skutečně potřebuješ, nebo přepni řadu na verzi Go, která ho splňuje.
Build je zabit bez chyby od Go. V překladači došla paměť. Zkus go build -p 1, odstraň těžké závislosti, nebo přejdi na vyšší tarif pro plán, na kterém buildíš.
Restarty každých pár minut, graf paměti stoupá. Narážíš na limit. Nastav GOMEMLIMIT a pak hledej únik. Pamatuj, že tři restarty za hodinu vyvolají varování na stránce serveru a šest vede k pozastavení.
`dial tcp: lookup db: no such host`. Host databáze je pro toto prostředí špatný. Použij host a port, které ti dal panel nebo tarif databáze, ne jméno z lokálního souboru compose.
Každý návštěvník má v logu stejnou IP. Čteš r.RemoteAddr. Čti X-Forwarded-For, jak je popsáno výše.
`x509: certificate signed by unknown authority` u odchozích volání. Prostředí nemá použitelný balík CA, nebo server, na který voláš, používá soukromou CA. Odchozí požadavek vyzkoušej při prvním deployi, abys na to narazil brzy.
Požadavky visí a po dlouhém čekání selžou. Handlery volají ven bez deadlinu. Použij http.Client{Timeout: ...} a předávej r.Context() do volání databáze a HTTP, aby zrušený požadavek přestal pracovat.
FAQ#
Na kterou verzi Go mám cílit?
Na nejnižší, kterou tvůj kód skutečně potřebuje, zapsanou v řádku go souboru go.mod. Cokoli napíšeš s 1.22 nebo novější, dostane ServeMux s podporou metod, a 1.21 přidala log/slog. Zkontroluj verzi, kterou řada Go poskytuje, a drž direktivu go na její úrovni nebo pod ní.
Potřebuji chi nebo gin?
Ne. Standardní knihovna stačí na většinu API a handlery psané pro ni se do chi přenesou beze změn. Router přidej, když chceš skupiny rout a řetězení middlewarů, a gin, když chceš jeho binding požadavků a validaci.
Jak získám skutečnou IP klienta?
Čti X-Forwarded-For, kterou nastavuje proxy, a použij poslední záznam, který připojila, ne první. r.RemoteAddr je adresa proxy a bude stejná pro každého návštěvníka.
Proč restart trvá déle než u aplikace v Node nebo Pythonu?
Server při každém startu spouští go build, takže restart zahrnuje kompilaci. Je rychlá, když se změnilo málo, a pomalejší za studena. Štíhlé závislosti ji udrží krátkou.
Můžu spouštět migrace při startu?
Můžeš a u hračky to nevadí. Pro cokoli ostrého spouštěj migrace jako samostatný krok, protože služba uvízlá ve smyčce restartů bude znovu zkoušet stejnou napůl aplikovanou změnu.
Mám nastavit GOMEMLIMIT?
Ano, pokud tvá služba drží znatelné množství haldy. Nastav ho o 10 až 20 procent pod paměť tarifu, aby se collector zpřísnil dřív, než se kontejner zastaví. Zabrání to garbage collectoru, aby bral celý stroj jako svůj vlastní.


