[{"data":1,"prerenderedAt":4733},["ShallowReactive",2],{"content:\u002F05-web\u002F01-http":3},{"title":4,"description":5,"path":6,"body":7},"Пакет http в Go","net\u002Fhttp — один из лучших примеров того, как стандартная библиотека Go закрывает большинство потребностей без внешних зависимостей. На нём построены тысячи production-сервисов, а популярные фреймворки вроде Gin и Echo — это просто тонкие обёртки поверх него. Понять net\u002Fhttp изнутри значит понять как работает любой Go веб-фреймворк.","\u002F05-web\u002F01-http",{"type":8,"value":9,"toc":4709},"minimark",[10,15,26,29,34,37,96,109,326,331,338,499,501,505,511,600,604,611,805,808,810,814,818,1086,1090,1232,1249,1253,1256,1715,1717,1721,1731,1762,1766,2063,2067,2295,2299,2488,2492,2716,2718,2722,2727,3191,3193,3197,3200,3783,3785,3789,3800,3877,3885,3887,3891,3908,3927,3939,3947,3959,3978,3994,4003,4020,4022,4024,4028,4089,4364,4695,4705],[11,12,14],"h1",{"id":13},"пакет-nethttp-в-go","Пакет net\u002Fhttp в Go",[16,17,18,22,23,25],"p",{},[19,20,21],"code",{},"net\u002Fhttp"," — один из лучших примеров того, как стандартная библиотека Go закрывает большинство потребностей без внешних зависимостей. На нём построены тысячи production-сервисов, а популярные фреймворки вроде Gin и Echo — это просто тонкие обёртки поверх него. Понять ",[19,24,21],{}," изнутри значит понять как работает любой Go веб-фреймворк.",[27,28],"hr",{},[30,31,33],"h2",{"id":32},"как-устроен-http-сервер-в-go","Как устроен HTTP-сервер в Go",[16,35,36],{},"В основе всего один интерфейс:",[38,39,44],"pre",{"className":40,"code":41,"language":42,"meta":43,"style":43},"language-go shiki shiki-themes github-dark","type Handler interface {\n    ServeHTTP(ResponseWriter, *Request)\n}\n","go","",[19,45,46,66,90],{"__ignoreMap":43},[47,48,51,55,59,62],"span",{"class":49,"line":50},"line",1,[47,52,54],{"class":53},"snl16","type",[47,56,58],{"class":57},"svObZ"," Handler",[47,60,61],{"class":53}," interface",[47,63,65],{"class":64},"s95oV"," {\n",[47,67,69,72,75,78,81,84,87],{"class":49,"line":68},2,[47,70,71],{"class":57},"    ServeHTTP",[47,73,74],{"class":64},"(",[47,76,77],{"class":57},"ResponseWriter",[47,79,80],{"class":64},", ",[47,82,83],{"class":53},"*",[47,85,86],{"class":57},"Request",[47,88,89],{"class":64},")\n",[47,91,93],{"class":49,"line":92},3,[47,94,95],{"class":64},"}\n",[16,97,98,99,101,102,105,106,108],{},"Весь ",[19,100,21],{}," построен вокруг этого интерфейса. Любой тип, реализующий ",[19,103,104],{},"ServeHTTP"," — это HTTP-обработчик. Сервер принимает соединение, парсит запрос и вызывает ",[19,107,104],{}," нужного обработчика.",[38,110,113],{"className":40,"code":111,"language":42,"meta":112,"style":43},"\u002F\u002F Минимальный рабочий сервер\n\u002F\u002F (запуск на локальной машине: go run main.go)\npackage main\n\nimport (\n    \"fmt\"\n    \"net\u002Fhttp\"\n)\n\ntype HelloHandler struct{}\n\nfunc (h HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n    fmt.Fprintln(w, \"Hello, World!\")\n}\n\nfunc main() {\n    handler := HelloHandler{}\n    http.ListenAndServe(\":8080\", handler)\n}\n","no-run",[19,114,115,121,126,134,141,150,163,172,177,182,196,201,253,270,275,280,291,304,321],{"__ignoreMap":43},[47,116,117],{"class":49,"line":50},[47,118,120],{"class":119},"sAwPA","\u002F\u002F Минимальный рабочий сервер\n",[47,122,123],{"class":49,"line":68},[47,124,125],{"class":119},"\u002F\u002F (запуск на локальной машине: go run main.go)\n",[47,127,128,131],{"class":49,"line":92},[47,129,130],{"class":53},"package",[47,132,133],{"class":57}," main\n",[47,135,137],{"class":49,"line":136},4,[47,138,140],{"emptyLinePlaceholder":139},true,"\n",[47,142,144,147],{"class":49,"line":143},5,[47,145,146],{"class":53},"import",[47,148,149],{"class":64}," (\n",[47,151,153,157,160],{"class":49,"line":152},6,[47,154,156],{"class":155},"sU2Wk","    \"",[47,158,159],{"class":57},"fmt",[47,161,162],{"class":155},"\"\n",[47,164,166,168,170],{"class":49,"line":165},7,[47,167,156],{"class":155},[47,169,21],{"class":57},[47,171,162],{"class":155},[47,173,175],{"class":49,"line":174},8,[47,176,89],{"class":64},[47,178,180],{"class":49,"line":179},9,[47,181,140],{"emptyLinePlaceholder":139},[47,183,185,187,190,193],{"class":49,"line":184},10,[47,186,54],{"class":53},[47,188,189],{"class":57}," HelloHandler",[47,191,192],{"class":53}," struct",[47,194,195],{"class":64},"{}\n",[47,197,199],{"class":49,"line":198},11,[47,200,140],{"emptyLinePlaceholder":139},[47,202,204,207,210,214,217,220,222,224,227,230,233,235,237,240,243,246,248,250],{"class":49,"line":203},12,[47,205,206],{"class":53},"func",[47,208,209],{"class":64}," (",[47,211,213],{"class":212},"s9osk","h ",[47,215,216],{"class":57},"HelloHandler",[47,218,219],{"class":64},") ",[47,221,104],{"class":57},[47,223,74],{"class":64},[47,225,226],{"class":212},"w",[47,228,229],{"class":57}," http",[47,231,232],{"class":64},".",[47,234,77],{"class":57},[47,236,80],{"class":64},[47,238,239],{"class":212},"r",[47,241,242],{"class":53}," *",[47,244,245],{"class":57},"http",[47,247,232],{"class":64},[47,249,86],{"class":57},[47,251,252],{"class":64},") {\n",[47,254,256,259,262,265,268],{"class":49,"line":255},13,[47,257,258],{"class":64},"    fmt.",[47,260,261],{"class":57},"Fprintln",[47,263,264],{"class":64},"(w, ",[47,266,267],{"class":155},"\"Hello, World!\"",[47,269,89],{"class":64},[47,271,273],{"class":49,"line":272},14,[47,274,95],{"class":64},[47,276,278],{"class":49,"line":277},15,[47,279,140],{"emptyLinePlaceholder":139},[47,281,283,285,288],{"class":49,"line":282},16,[47,284,206],{"class":53},[47,286,287],{"class":57}," main",[47,289,290],{"class":64},"() {\n",[47,292,294,297,300,302],{"class":49,"line":293},17,[47,295,296],{"class":64},"    handler ",[47,298,299],{"class":53},":=",[47,301,189],{"class":57},[47,303,195],{"class":64},[47,305,307,310,313,315,318],{"class":49,"line":306},18,[47,308,309],{"class":64},"    http.",[47,311,312],{"class":57},"ListenAndServe",[47,314,74],{"class":64},[47,316,317],{"class":155},"\":8080\"",[47,319,320],{"class":64},", handler)\n",[47,322,324],{"class":49,"line":323},19,[47,325,95],{"class":64},[327,328,330],"h3",{"id":329},"handlerfunc-функция-как-обработчик","HandlerFunc — функция как обработчик",[16,332,333,334,337],{},"Реализовывать интерфейс для каждого обработчика — многословно. ",[19,335,336],{},"http.HandlerFunc"," позволяет использовать функцию напрямую:",[38,339,341],{"className":40,"code":340,"language":42,"meta":112,"style":43},"\u002F\u002F http.HandlerFunc — это просто тип-адаптер\ntype HandlerFunc func(ResponseWriter, *Request)\n\nfunc (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {\n    f(w, r)\n}\n\n\u002F\u002F Использование\nfunc helloHandler(w http.ResponseWriter, r *http.Request) {\n    fmt.Fprintln(w, \"Hello!\")\n}\n\nhttp.ListenAndServe(\":8080\", http.HandlerFunc(helloHandler))\n",[19,342,343,348,370,374,407,415,419,423,428,459,472,476,480],{"__ignoreMap":43},[47,344,345],{"class":49,"line":50},[47,346,347],{"class":119},"\u002F\u002F http.HandlerFunc — это просто тип-адаптер\n",[47,349,350,352,355,358,360,362,364,366,368],{"class":49,"line":68},[47,351,54],{"class":53},[47,353,354],{"class":57}," HandlerFunc",[47,356,357],{"class":53}," func",[47,359,74],{"class":64},[47,361,77],{"class":57},[47,363,80],{"class":64},[47,365,83],{"class":53},[47,367,86],{"class":57},[47,369,89],{"class":64},[47,371,372],{"class":49,"line":92},[47,373,140],{"emptyLinePlaceholder":139},[47,375,376,378,380,383,386,388,390,392,394,397,399,401,403,405],{"class":49,"line":136},[47,377,206],{"class":53},[47,379,209],{"class":64},[47,381,382],{"class":212},"f ",[47,384,385],{"class":57},"HandlerFunc",[47,387,219],{"class":64},[47,389,104],{"class":57},[47,391,74],{"class":64},[47,393,226],{"class":212},[47,395,396],{"class":57}," ResponseWriter",[47,398,80],{"class":64},[47,400,239],{"class":212},[47,402,242],{"class":53},[47,404,86],{"class":57},[47,406,252],{"class":64},[47,408,409,412],{"class":49,"line":143},[47,410,411],{"class":57},"    f",[47,413,414],{"class":64},"(w, r)\n",[47,416,417],{"class":49,"line":152},[47,418,95],{"class":64},[47,420,421],{"class":49,"line":165},[47,422,140],{"emptyLinePlaceholder":139},[47,424,425],{"class":49,"line":174},[47,426,427],{"class":119},"\u002F\u002F Использование\n",[47,429,430,432,435,437,439,441,443,445,447,449,451,453,455,457],{"class":49,"line":179},[47,431,206],{"class":53},[47,433,434],{"class":57}," helloHandler",[47,436,74],{"class":64},[47,438,226],{"class":212},[47,440,229],{"class":57},[47,442,232],{"class":64},[47,444,77],{"class":57},[47,446,80],{"class":64},[47,448,239],{"class":212},[47,450,242],{"class":53},[47,452,245],{"class":57},[47,454,232],{"class":64},[47,456,86],{"class":57},[47,458,252],{"class":64},[47,460,461,463,465,467,470],{"class":49,"line":184},[47,462,258],{"class":64},[47,464,261],{"class":57},[47,466,264],{"class":64},[47,468,469],{"class":155},"\"Hello!\"",[47,471,89],{"class":64},[47,473,474],{"class":49,"line":198},[47,475,95],{"class":64},[47,477,478],{"class":49,"line":203},[47,479,140],{"emptyLinePlaceholder":139},[47,481,482,485,487,489,491,494,496],{"class":49,"line":255},[47,483,484],{"class":64},"http.",[47,486,312],{"class":57},[47,488,74],{"class":64},[47,490,317],{"class":155},[47,492,493],{"class":64},", http.",[47,495,385],{"class":57},[47,497,498],{"class":64},"(helloHandler))\n",[27,500],{},[30,502,504],{"id":503},"servemux-маршрутизатор","ServeMux — маршрутизатор",[16,506,507,510],{},[19,508,509],{},"http.ServeMux"," — стандартный маршрутизатор. Сопоставляет URL-паттерны с обработчиками:",[38,512,514],{"className":40,"code":513,"language":42,"meta":112,"style":43},"mux := http.NewServeMux()\n\nmux.HandleFunc(\"\u002F\", homeHandler)\nmux.HandleFunc(\"\u002Fusers\", usersHandler)\nmux.HandleFunc(\"\u002Fusers\u002F\", userHandler) \u002F\u002F trailing slash — ловит всё начинающееся с \u002Fusers\u002F\n\nhttp.ListenAndServe(\":8080\", mux)\n",[19,515,516,532,536,552,566,583,587],{"__ignoreMap":43},[47,517,518,521,523,526,529],{"class":49,"line":50},[47,519,520],{"class":64},"mux ",[47,522,299],{"class":53},[47,524,525],{"class":64}," http.",[47,527,528],{"class":57},"NewServeMux",[47,530,531],{"class":64},"()\n",[47,533,534],{"class":49,"line":68},[47,535,140],{"emptyLinePlaceholder":139},[47,537,538,541,544,546,549],{"class":49,"line":92},[47,539,540],{"class":64},"mux.",[47,542,543],{"class":57},"HandleFunc",[47,545,74],{"class":64},[47,547,548],{"class":155},"\"\u002F\"",[47,550,551],{"class":64},", homeHandler)\n",[47,553,554,556,558,560,563],{"class":49,"line":136},[47,555,540],{"class":64},[47,557,543],{"class":57},[47,559,74],{"class":64},[47,561,562],{"class":155},"\"\u002Fusers\"",[47,564,565],{"class":64},", usersHandler)\n",[47,567,568,570,572,574,577,580],{"class":49,"line":143},[47,569,540],{"class":64},[47,571,543],{"class":57},[47,573,74],{"class":64},[47,575,576],{"class":155},"\"\u002Fusers\u002F\"",[47,578,579],{"class":64},", userHandler) ",[47,581,582],{"class":119},"\u002F\u002F trailing slash — ловит всё начинающееся с \u002Fusers\u002F\n",[47,584,585],{"class":49,"line":152},[47,586,140],{"emptyLinePlaceholder":139},[47,588,589,591,593,595,597],{"class":49,"line":165},[47,590,484],{"class":64},[47,592,312],{"class":57},[47,594,74],{"class":64},[47,596,317],{"class":155},[47,598,599],{"class":64},", mux)\n",[327,601,603],{"id":602},"улучшенный-маршрутизатор-в-go-122","Улучшенный маршрутизатор в Go 1.22",[16,605,606,607,610],{},"До Go 1.22 стандартный ServeMux был ограничен — нельзя было указать HTTP-метод или path параметры (",[19,608,609],{},"\u002Fusers\u002F{id}","). Это главная причина популярности сторонних роутеров. В Go 1.22 маршрутизатор значительно улучшили:",[38,612,614],{"className":40,"code":613,"language":42,"meta":112,"style":43},"mux := http.NewServeMux()\n\n\u002F\u002F Метод + путь\nmux.HandleFunc(\"GET \u002Fusers\", listUsers)\nmux.HandleFunc(\"POST \u002Fusers\", createUser)\n\n\u002F\u002F Path параметры через {name}\nmux.HandleFunc(\"GET \u002Fusers\u002F{id}\", getUser)\nmux.HandleFunc(\"PUT \u002Fusers\u002F{id}\", updateUser)\nmux.HandleFunc(\"DELETE \u002Fusers\u002F{id}\", deleteUser)\n\n\u002F\u002F Получение path параметра\nfunc getUser(w http.ResponseWriter, r *http.Request) {\n    id := r.PathValue(\"id\") \u002F\u002F Go 1.22+\n    fmt.Fprintf(w, \"user id: %s\", id)\n}\n",[19,615,616,628,632,637,651,665,669,674,688,702,716,720,725,756,779,801],{"__ignoreMap":43},[47,617,618,620,622,624,626],{"class":49,"line":50},[47,619,520],{"class":64},[47,621,299],{"class":53},[47,623,525],{"class":64},[47,625,528],{"class":57},[47,627,531],{"class":64},[47,629,630],{"class":49,"line":68},[47,631,140],{"emptyLinePlaceholder":139},[47,633,634],{"class":49,"line":92},[47,635,636],{"class":119},"\u002F\u002F Метод + путь\n",[47,638,639,641,643,645,648],{"class":49,"line":136},[47,640,540],{"class":64},[47,642,543],{"class":57},[47,644,74],{"class":64},[47,646,647],{"class":155},"\"GET \u002Fusers\"",[47,649,650],{"class":64},", listUsers)\n",[47,652,653,655,657,659,662],{"class":49,"line":143},[47,654,540],{"class":64},[47,656,543],{"class":57},[47,658,74],{"class":64},[47,660,661],{"class":155},"\"POST \u002Fusers\"",[47,663,664],{"class":64},", createUser)\n",[47,666,667],{"class":49,"line":152},[47,668,140],{"emptyLinePlaceholder":139},[47,670,671],{"class":49,"line":165},[47,672,673],{"class":119},"\u002F\u002F Path параметры через {name}\n",[47,675,676,678,680,682,685],{"class":49,"line":174},[47,677,540],{"class":64},[47,679,543],{"class":57},[47,681,74],{"class":64},[47,683,684],{"class":155},"\"GET \u002Fusers\u002F{id}\"",[47,686,687],{"class":64},", getUser)\n",[47,689,690,692,694,696,699],{"class":49,"line":179},[47,691,540],{"class":64},[47,693,543],{"class":57},[47,695,74],{"class":64},[47,697,698],{"class":155},"\"PUT \u002Fusers\u002F{id}\"",[47,700,701],{"class":64},", updateUser)\n",[47,703,704,706,708,710,713],{"class":49,"line":184},[47,705,540],{"class":64},[47,707,543],{"class":57},[47,709,74],{"class":64},[47,711,712],{"class":155},"\"DELETE \u002Fusers\u002F{id}\"",[47,714,715],{"class":64},", deleteUser)\n",[47,717,718],{"class":49,"line":198},[47,719,140],{"emptyLinePlaceholder":139},[47,721,722],{"class":49,"line":203},[47,723,724],{"class":119},"\u002F\u002F Получение path параметра\n",[47,726,727,729,732,734,736,738,740,742,744,746,748,750,752,754],{"class":49,"line":255},[47,728,206],{"class":53},[47,730,731],{"class":57}," getUser",[47,733,74],{"class":64},[47,735,226],{"class":212},[47,737,229],{"class":57},[47,739,232],{"class":64},[47,741,77],{"class":57},[47,743,80],{"class":64},[47,745,239],{"class":212},[47,747,242],{"class":53},[47,749,245],{"class":57},[47,751,232],{"class":64},[47,753,86],{"class":57},[47,755,252],{"class":64},[47,757,758,761,763,766,769,771,774,776],{"class":49,"line":272},[47,759,760],{"class":64},"    id ",[47,762,299],{"class":53},[47,764,765],{"class":64}," r.",[47,767,768],{"class":57},"PathValue",[47,770,74],{"class":64},[47,772,773],{"class":155},"\"id\"",[47,775,219],{"class":64},[47,777,778],{"class":119},"\u002F\u002F Go 1.22+\n",[47,780,781,783,786,788,791,795,798],{"class":49,"line":277},[47,782,258],{"class":64},[47,784,785],{"class":57},"Fprintf",[47,787,264],{"class":64},[47,789,790],{"class":155},"\"user id: ",[47,792,794],{"class":793},"sDLfK","%s",[47,796,797],{"class":155},"\"",[47,799,800],{"class":64},", id)\n",[47,802,803],{"class":49,"line":282},[47,804,95],{"class":64},[16,806,807],{},"Стандартный маршрутизатор Go 1.22 закрывает большинство базовых потребностей. Для сложных случаев (middleware chains, группы роутов, валидация) всё ещё удобнее фреймворки.",[27,809],{},[30,811,813],{"id":812},"responsewriter-и-request","ResponseWriter и Request",[327,815,817],{"id":816},"httprequest-входящий-запрос","http.Request — входящий запрос",[38,819,821],{"className":40,"code":820,"language":42,"meta":43,"style":43},"func handler(w http.ResponseWriter, r *http.Request) {\n    \u002F\u002F Метод и URL\n    fmt.Println(r.Method)       \u002F\u002F \"GET\", \"POST\", ...\n    fmt.Println(r.URL.Path)     \u002F\u002F \"\u002Fusers\u002F42\"\n    fmt.Println(r.URL.Query())  \u002F\u002F query параметры: ?page=1&limit=10\n\n    \u002F\u002F Query параметры\n    page := r.URL.Query().Get(\"page\")\n    limit := r.URL.Query().Get(\"limit\")\n\n    \u002F\u002F Заголовки\n    token := r.Header.Get(\"Authorization\")\n    contentType := r.Header.Get(\"Content-Type\")\n\n    \u002F\u002F Тело запроса\n    body, err := io.ReadAll(r.Body)\n    \u002F\u002F В server-side handler тело запроса закрывает net\u002Fhttp.\n    \u002F\u002F Важно ограничивать размер body и корректно обрабатывать ошибку чтения.\n\n    \u002F\u002F Контекст запроса\n    ctx := r.Context()\n    userID := ctx.Value(userIDKey)\n}\n",[19,822,823,854,859,872,884,902,906,911,936,958,962,967,986,1004,1008,1013,1029,1034,1039,1043,1049,1064,1081],{"__ignoreMap":43},[47,824,825,827,830,832,834,836,838,840,842,844,846,848,850,852],{"class":49,"line":50},[47,826,206],{"class":53},[47,828,829],{"class":57}," handler",[47,831,74],{"class":64},[47,833,226],{"class":212},[47,835,229],{"class":57},[47,837,232],{"class":64},[47,839,77],{"class":57},[47,841,80],{"class":64},[47,843,239],{"class":212},[47,845,242],{"class":53},[47,847,245],{"class":57},[47,849,232],{"class":64},[47,851,86],{"class":57},[47,853,252],{"class":64},[47,855,856],{"class":49,"line":68},[47,857,858],{"class":119},"    \u002F\u002F Метод и URL\n",[47,860,861,863,866,869],{"class":49,"line":92},[47,862,258],{"class":64},[47,864,865],{"class":57},"Println",[47,867,868],{"class":64},"(r.Method)       ",[47,870,871],{"class":119},"\u002F\u002F \"GET\", \"POST\", ...\n",[47,873,874,876,878,881],{"class":49,"line":136},[47,875,258],{"class":64},[47,877,865],{"class":57},[47,879,880],{"class":64},"(r.URL.Path)     ",[47,882,883],{"class":119},"\u002F\u002F \"\u002Fusers\u002F42\"\n",[47,885,886,888,890,893,896,899],{"class":49,"line":143},[47,887,258],{"class":64},[47,889,865],{"class":57},[47,891,892],{"class":64},"(r.URL.",[47,894,895],{"class":57},"Query",[47,897,898],{"class":64},"())  ",[47,900,901],{"class":119},"\u002F\u002F query параметры: ?page=1&limit=10\n",[47,903,904],{"class":49,"line":152},[47,905,140],{"emptyLinePlaceholder":139},[47,907,908],{"class":49,"line":165},[47,909,910],{"class":119},"    \u002F\u002F Query параметры\n",[47,912,913,916,918,921,923,926,929,931,934],{"class":49,"line":174},[47,914,915],{"class":64},"    page ",[47,917,299],{"class":53},[47,919,920],{"class":64}," r.URL.",[47,922,895],{"class":57},[47,924,925],{"class":64},"().",[47,927,928],{"class":57},"Get",[47,930,74],{"class":64},[47,932,933],{"class":155},"\"page\"",[47,935,89],{"class":64},[47,937,938,941,943,945,947,949,951,953,956],{"class":49,"line":179},[47,939,940],{"class":64},"    limit ",[47,942,299],{"class":53},[47,944,920],{"class":64},[47,946,895],{"class":57},[47,948,925],{"class":64},[47,950,928],{"class":57},[47,952,74],{"class":64},[47,954,955],{"class":155},"\"limit\"",[47,957,89],{"class":64},[47,959,960],{"class":49,"line":184},[47,961,140],{"emptyLinePlaceholder":139},[47,963,964],{"class":49,"line":198},[47,965,966],{"class":119},"    \u002F\u002F Заголовки\n",[47,968,969,972,974,977,979,981,984],{"class":49,"line":203},[47,970,971],{"class":64},"    token ",[47,973,299],{"class":53},[47,975,976],{"class":64}," r.Header.",[47,978,928],{"class":57},[47,980,74],{"class":64},[47,982,983],{"class":155},"\"Authorization\"",[47,985,89],{"class":64},[47,987,988,991,993,995,997,999,1002],{"class":49,"line":255},[47,989,990],{"class":64},"    contentType ",[47,992,299],{"class":53},[47,994,976],{"class":64},[47,996,928],{"class":57},[47,998,74],{"class":64},[47,1000,1001],{"class":155},"\"Content-Type\"",[47,1003,89],{"class":64},[47,1005,1006],{"class":49,"line":272},[47,1007,140],{"emptyLinePlaceholder":139},[47,1009,1010],{"class":49,"line":277},[47,1011,1012],{"class":119},"    \u002F\u002F Тело запроса\n",[47,1014,1015,1018,1020,1023,1026],{"class":49,"line":282},[47,1016,1017],{"class":64},"    body, err ",[47,1019,299],{"class":53},[47,1021,1022],{"class":64}," io.",[47,1024,1025],{"class":57},"ReadAll",[47,1027,1028],{"class":64},"(r.Body)\n",[47,1030,1031],{"class":49,"line":293},[47,1032,1033],{"class":119},"    \u002F\u002F В server-side handler тело запроса закрывает net\u002Fhttp.\n",[47,1035,1036],{"class":49,"line":306},[47,1037,1038],{"class":119},"    \u002F\u002F Важно ограничивать размер body и корректно обрабатывать ошибку чтения.\n",[47,1040,1041],{"class":49,"line":323},[47,1042,140],{"emptyLinePlaceholder":139},[47,1044,1046],{"class":49,"line":1045},20,[47,1047,1048],{"class":119},"    \u002F\u002F Контекст запроса\n",[47,1050,1052,1055,1057,1059,1062],{"class":49,"line":1051},21,[47,1053,1054],{"class":64},"    ctx ",[47,1056,299],{"class":53},[47,1058,765],{"class":64},[47,1060,1061],{"class":57},"Context",[47,1063,531],{"class":64},[47,1065,1067,1070,1072,1075,1078],{"class":49,"line":1066},22,[47,1068,1069],{"class":64},"    userID ",[47,1071,299],{"class":53},[47,1073,1074],{"class":64}," ctx.",[47,1076,1077],{"class":57},"Value",[47,1079,1080],{"class":64},"(userIDKey)\n",[47,1082,1084],{"class":49,"line":1083},23,[47,1085,95],{"class":64},[327,1087,1089],{"id":1088},"httpresponsewriter-исходящий-ответ","http.ResponseWriter — исходящий ответ",[38,1091,1093],{"className":40,"code":1092,"language":42,"meta":43,"style":43},"func handler(w http.ResponseWriter, r *http.Request) {\n    \u002F\u002F Заголовки нужно писать ДО WriteHeader и Write\n    w.Header().Set(\"Content-Type\", \"application\u002Fjson\")\n    w.Header().Set(\"X-Request-ID\", \"abc-123\")\n\n    \u002F\u002F Статус код — тоже до Write\n    w.WriteHeader(http.StatusCreated) \u002F\u002F 201\n\n    \u002F\u002F Тело ответа\n    w.Write([]byte(`{\"id\": 1}`))\n}\n",[19,1094,1095,1125,1130,1154,1176,1180,1185,1198,1202,1207,1228],{"__ignoreMap":43},[47,1096,1097,1099,1101,1103,1105,1107,1109,1111,1113,1115,1117,1119,1121,1123],{"class":49,"line":50},[47,1098,206],{"class":53},[47,1100,829],{"class":57},[47,1102,74],{"class":64},[47,1104,226],{"class":212},[47,1106,229],{"class":57},[47,1108,232],{"class":64},[47,1110,77],{"class":57},[47,1112,80],{"class":64},[47,1114,239],{"class":212},[47,1116,242],{"class":53},[47,1118,245],{"class":57},[47,1120,232],{"class":64},[47,1122,86],{"class":57},[47,1124,252],{"class":64},[47,1126,1127],{"class":49,"line":68},[47,1128,1129],{"class":119},"    \u002F\u002F Заголовки нужно писать ДО WriteHeader и Write\n",[47,1131,1132,1135,1138,1140,1143,1145,1147,1149,1152],{"class":49,"line":92},[47,1133,1134],{"class":64},"    w.",[47,1136,1137],{"class":57},"Header",[47,1139,925],{"class":64},[47,1141,1142],{"class":57},"Set",[47,1144,74],{"class":64},[47,1146,1001],{"class":155},[47,1148,80],{"class":64},[47,1150,1151],{"class":155},"\"application\u002Fjson\"",[47,1153,89],{"class":64},[47,1155,1156,1158,1160,1162,1164,1166,1169,1171,1174],{"class":49,"line":136},[47,1157,1134],{"class":64},[47,1159,1137],{"class":57},[47,1161,925],{"class":64},[47,1163,1142],{"class":57},[47,1165,74],{"class":64},[47,1167,1168],{"class":155},"\"X-Request-ID\"",[47,1170,80],{"class":64},[47,1172,1173],{"class":155},"\"abc-123\"",[47,1175,89],{"class":64},[47,1177,1178],{"class":49,"line":143},[47,1179,140],{"emptyLinePlaceholder":139},[47,1181,1182],{"class":49,"line":152},[47,1183,1184],{"class":119},"    \u002F\u002F Статус код — тоже до Write\n",[47,1186,1187,1189,1192,1195],{"class":49,"line":165},[47,1188,1134],{"class":64},[47,1190,1191],{"class":57},"WriteHeader",[47,1193,1194],{"class":64},"(http.StatusCreated) ",[47,1196,1197],{"class":119},"\u002F\u002F 201\n",[47,1199,1200],{"class":49,"line":174},[47,1201,140],{"emptyLinePlaceholder":139},[47,1203,1204],{"class":49,"line":179},[47,1205,1206],{"class":119},"    \u002F\u002F Тело ответа\n",[47,1208,1209,1211,1214,1217,1220,1222,1225],{"class":49,"line":184},[47,1210,1134],{"class":64},[47,1212,1213],{"class":57},"Write",[47,1215,1216],{"class":64},"([]",[47,1218,1219],{"class":53},"byte",[47,1221,74],{"class":64},[47,1223,1224],{"class":155},"`{\"id\": 1}`",[47,1226,1227],{"class":64},"))\n",[47,1229,1230],{"class":49,"line":198},[47,1231,95],{"class":64},[16,1233,1234,1235,1238,1239,1238,1242,1245,1246,1248],{},"Порядок важен: ",[19,1236,1237],{},"Header().Set()"," → ",[19,1240,1241],{},"WriteHeader()",[19,1243,1244],{},"Write()",". Попытка установить заголовок после ",[19,1247,1213],{}," — молчаливо игнорируется, Go выведет предупреждение в лог.",[327,1250,1252],{"id":1251},"работа-с-json","Работа с JSON",[16,1254,1255],{},"В реальных API постоянно нужно читать и писать JSON. Удобно вынести в хелперы:",[38,1257,1259],{"className":40,"code":1258,"language":42,"meta":43,"style":43},"\u002F\u002F Декодирование запроса\nfunc decodeJSON(r *http.Request, dst interface{}) error {\n    defer r.Body.Close()\n    decoder := json.NewDecoder(r.Body)\n    decoder.DisallowUnknownFields() \u002F\u002F строгий режим\n    return decoder.Decode(dst)\n}\n\n\u002F\u002F Отправка JSON-ответа\nfunc writeJSON(w http.ResponseWriter, status int, data interface{}) {\n    w.Header().Set(\"Content-Type\", \"application\u002Fjson\")\n    w.WriteHeader(status)\n    json.NewEncoder(w).Encode(data)\n}\n\n\u002F\u002F Отправка ошибки\nfunc writeError(w http.ResponseWriter, status int, message string) {\n    writeJSON(w, status, map[string]string{\"error\": message})\n}\n\n\u002F\u002F Использование\nfunc createUserHandler(w http.ResponseWriter, r *http.Request) {\n    var req CreateUserRequest\n    if err := decodeJSON(r, &req); err != nil {\n        writeError(w, http.StatusBadRequest, \"invalid request body\")\n        return\n    }\n\n    user, err := createUser(r.Context(), req)\n    if err != nil {\n        writeError(w, http.StatusInternalServerError, \"failed to create user\")\n        return\n    }\n\n    writeJSON(w, http.StatusCreated, user)\n}\n",[19,1260,1261,1266,1300,1313,1328,1342,1356,1360,1364,1369,1404,1424,1433,1450,1454,1458,1463,1496,1527,1531,1535,1539,1570,1581,1611,1625,1631,1637,1642,1661,1674,1687,1692,1697,1702,1710],{"__ignoreMap":43},[47,1262,1263],{"class":49,"line":50},[47,1264,1265],{"class":119},"\u002F\u002F Декодирование запроса\n",[47,1267,1268,1270,1273,1275,1277,1279,1281,1283,1285,1287,1290,1292,1295,1298],{"class":49,"line":68},[47,1269,206],{"class":53},[47,1271,1272],{"class":57}," decodeJSON",[47,1274,74],{"class":64},[47,1276,239],{"class":212},[47,1278,242],{"class":53},[47,1280,245],{"class":57},[47,1282,232],{"class":64},[47,1284,86],{"class":57},[47,1286,80],{"class":64},[47,1288,1289],{"class":212},"dst",[47,1291,61],{"class":53},[47,1293,1294],{"class":64},"{}) ",[47,1296,1297],{"class":53},"error",[47,1299,65],{"class":64},[47,1301,1302,1305,1308,1311],{"class":49,"line":92},[47,1303,1304],{"class":53},"    defer",[47,1306,1307],{"class":64}," r.Body.",[47,1309,1310],{"class":57},"Close",[47,1312,531],{"class":64},[47,1314,1315,1318,1320,1323,1326],{"class":49,"line":136},[47,1316,1317],{"class":64},"    decoder ",[47,1319,299],{"class":53},[47,1321,1322],{"class":64}," json.",[47,1324,1325],{"class":57},"NewDecoder",[47,1327,1028],{"class":64},[47,1329,1330,1333,1336,1339],{"class":49,"line":143},[47,1331,1332],{"class":64},"    decoder.",[47,1334,1335],{"class":57},"DisallowUnknownFields",[47,1337,1338],{"class":64},"() ",[47,1340,1341],{"class":119},"\u002F\u002F строгий режим\n",[47,1343,1344,1347,1350,1353],{"class":49,"line":152},[47,1345,1346],{"class":53},"    return",[47,1348,1349],{"class":64}," decoder.",[47,1351,1352],{"class":57},"Decode",[47,1354,1355],{"class":64},"(dst)\n",[47,1357,1358],{"class":49,"line":165},[47,1359,95],{"class":64},[47,1361,1362],{"class":49,"line":174},[47,1363,140],{"emptyLinePlaceholder":139},[47,1365,1366],{"class":49,"line":179},[47,1367,1368],{"class":119},"\u002F\u002F Отправка JSON-ответа\n",[47,1370,1371,1373,1376,1378,1380,1382,1384,1386,1388,1391,1394,1396,1399,1401],{"class":49,"line":184},[47,1372,206],{"class":53},[47,1374,1375],{"class":57}," writeJSON",[47,1377,74],{"class":64},[47,1379,226],{"class":212},[47,1381,229],{"class":57},[47,1383,232],{"class":64},[47,1385,77],{"class":57},[47,1387,80],{"class":64},[47,1389,1390],{"class":212},"status",[47,1392,1393],{"class":53}," int",[47,1395,80],{"class":64},[47,1397,1398],{"class":212},"data",[47,1400,61],{"class":53},[47,1402,1403],{"class":64},"{}) {\n",[47,1405,1406,1408,1410,1412,1414,1416,1418,1420,1422],{"class":49,"line":198},[47,1407,1134],{"class":64},[47,1409,1137],{"class":57},[47,1411,925],{"class":64},[47,1413,1142],{"class":57},[47,1415,74],{"class":64},[47,1417,1001],{"class":155},[47,1419,80],{"class":64},[47,1421,1151],{"class":155},[47,1423,89],{"class":64},[47,1425,1426,1428,1430],{"class":49,"line":203},[47,1427,1134],{"class":64},[47,1429,1191],{"class":57},[47,1431,1432],{"class":64},"(status)\n",[47,1434,1435,1438,1441,1444,1447],{"class":49,"line":255},[47,1436,1437],{"class":64},"    json.",[47,1439,1440],{"class":57},"NewEncoder",[47,1442,1443],{"class":64},"(w).",[47,1445,1446],{"class":57},"Encode",[47,1448,1449],{"class":64},"(data)\n",[47,1451,1452],{"class":49,"line":272},[47,1453,95],{"class":64},[47,1455,1456],{"class":49,"line":277},[47,1457,140],{"emptyLinePlaceholder":139},[47,1459,1460],{"class":49,"line":282},[47,1461,1462],{"class":119},"\u002F\u002F Отправка ошибки\n",[47,1464,1465,1467,1470,1472,1474,1476,1478,1480,1482,1484,1486,1488,1491,1494],{"class":49,"line":293},[47,1466,206],{"class":53},[47,1468,1469],{"class":57}," writeError",[47,1471,74],{"class":64},[47,1473,226],{"class":212},[47,1475,229],{"class":57},[47,1477,232],{"class":64},[47,1479,77],{"class":57},[47,1481,80],{"class":64},[47,1483,1390],{"class":212},[47,1485,1393],{"class":53},[47,1487,80],{"class":64},[47,1489,1490],{"class":212},"message",[47,1492,1493],{"class":53}," string",[47,1495,252],{"class":64},[47,1497,1498,1501,1504,1507,1510,1513,1516,1518,1521,1524],{"class":49,"line":306},[47,1499,1500],{"class":57},"    writeJSON",[47,1502,1503],{"class":64},"(w, status, ",[47,1505,1506],{"class":53},"map",[47,1508,1509],{"class":64},"[",[47,1511,1512],{"class":53},"string",[47,1514,1515],{"class":64},"]",[47,1517,1512],{"class":53},[47,1519,1520],{"class":64},"{",[47,1522,1523],{"class":155},"\"error\"",[47,1525,1526],{"class":64},": message})\n",[47,1528,1529],{"class":49,"line":323},[47,1530,95],{"class":64},[47,1532,1533],{"class":49,"line":1045},[47,1534,140],{"emptyLinePlaceholder":139},[47,1536,1537],{"class":49,"line":1051},[47,1538,427],{"class":119},[47,1540,1541,1543,1546,1548,1550,1552,1554,1556,1558,1560,1562,1564,1566,1568],{"class":49,"line":1066},[47,1542,206],{"class":53},[47,1544,1545],{"class":57}," createUserHandler",[47,1547,74],{"class":64},[47,1549,226],{"class":212},[47,1551,229],{"class":57},[47,1553,232],{"class":64},[47,1555,77],{"class":57},[47,1557,80],{"class":64},[47,1559,239],{"class":212},[47,1561,242],{"class":53},[47,1563,245],{"class":57},[47,1565,232],{"class":64},[47,1567,86],{"class":57},[47,1569,252],{"class":64},[47,1571,1572,1575,1578],{"class":49,"line":1083},[47,1573,1574],{"class":53},"    var",[47,1576,1577],{"class":64}," req ",[47,1579,1580],{"class":57},"CreateUserRequest\n",[47,1582,1584,1587,1590,1592,1594,1597,1600,1603,1606,1609],{"class":49,"line":1583},24,[47,1585,1586],{"class":53},"    if",[47,1588,1589],{"class":64}," err ",[47,1591,299],{"class":53},[47,1593,1272],{"class":57},[47,1595,1596],{"class":64},"(r, ",[47,1598,1599],{"class":53},"&",[47,1601,1602],{"class":64},"req); err ",[47,1604,1605],{"class":53},"!=",[47,1607,1608],{"class":793}," nil",[47,1610,65],{"class":64},[47,1612,1614,1617,1620,1623],{"class":49,"line":1613},25,[47,1615,1616],{"class":57},"        writeError",[47,1618,1619],{"class":64},"(w, http.StatusBadRequest, ",[47,1621,1622],{"class":155},"\"invalid request body\"",[47,1624,89],{"class":64},[47,1626,1628],{"class":49,"line":1627},26,[47,1629,1630],{"class":53},"        return\n",[47,1632,1634],{"class":49,"line":1633},27,[47,1635,1636],{"class":64},"    }\n",[47,1638,1640],{"class":49,"line":1639},28,[47,1641,140],{"emptyLinePlaceholder":139},[47,1643,1645,1648,1650,1653,1656,1658],{"class":49,"line":1644},29,[47,1646,1647],{"class":64},"    user, err ",[47,1649,299],{"class":53},[47,1651,1652],{"class":57}," createUser",[47,1654,1655],{"class":64},"(r.",[47,1657,1061],{"class":57},[47,1659,1660],{"class":64},"(), req)\n",[47,1662,1664,1666,1668,1670,1672],{"class":49,"line":1663},30,[47,1665,1586],{"class":53},[47,1667,1589],{"class":64},[47,1669,1605],{"class":53},[47,1671,1608],{"class":793},[47,1673,65],{"class":64},[47,1675,1677,1679,1682,1685],{"class":49,"line":1676},31,[47,1678,1616],{"class":57},[47,1680,1681],{"class":64},"(w, http.StatusInternalServerError, ",[47,1683,1684],{"class":155},"\"failed to create user\"",[47,1686,89],{"class":64},[47,1688,1690],{"class":49,"line":1689},32,[47,1691,1630],{"class":53},[47,1693,1695],{"class":49,"line":1694},33,[47,1696,1636],{"class":64},[47,1698,1700],{"class":49,"line":1699},34,[47,1701,140],{"emptyLinePlaceholder":139},[47,1703,1705,1707],{"class":49,"line":1704},35,[47,1706,1500],{"class":57},[47,1708,1709],{"class":64},"(w, http.StatusCreated, user)\n",[47,1711,1713],{"class":49,"line":1712},36,[47,1714,95],{"class":64},[27,1716],{},[30,1718,1720],{"id":1719},"middleware","Middleware",[16,1722,1723,1724,1727,1728,1730],{},"Middleware — обёртка вокруг обработчика, добавляющая поведение до или после его вызова. В Go middleware реализуется как функция, принимающая ",[19,1725,1726],{},"Handler"," и возвращающая ",[19,1729,1726],{},":",[38,1732,1734],{"className":40,"code":1733,"language":42,"meta":43,"style":43},"type Middleware func(http.Handler) http.Handler\n",[19,1735,1736],{"__ignoreMap":43},[47,1737,1738,1740,1743,1745,1747,1749,1751,1753,1755,1757,1759],{"class":49,"line":50},[47,1739,54],{"class":53},[47,1741,1742],{"class":57}," Middleware",[47,1744,357],{"class":53},[47,1746,74],{"class":64},[47,1748,245],{"class":57},[47,1750,232],{"class":64},[47,1752,1726],{"class":57},[47,1754,219],{"class":64},[47,1756,245],{"class":57},[47,1758,232],{"class":64},[47,1760,1761],{"class":57},"Handler\n",[327,1763,1765],{"id":1764},"логирование-запросов","Логирование запросов",[38,1767,1769],{"className":40,"code":1768,"language":42,"meta":43,"style":43},"func loggingMiddleware(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        start := time.Now()\n\n        \u002F\u002F Оборачиваем ResponseWriter чтобы перехватить статус код\n        wrapped := &responseWriter{ResponseWriter: w, status: http.StatusOK}\n\n        next.ServeHTTP(wrapped, r) \u002F\u002F вызываем следующий обработчик\n\n        log.Printf(\n            \"%s %s %d %v\",\n            r.Method,\n            r.URL.Path,\n            wrapped.status,\n            time.Since(start),\n        )\n    })\n}\n\n\u002F\u002F Обёртка для перехвата статус кода\ntype responseWriter struct {\n    http.ResponseWriter\n    status int\n}\n\nfunc (rw *responseWriter) WriteHeader(status int) {\n    rw.status = status\n    rw.ResponseWriter.WriteHeader(status)\n}\n",[19,1770,1771,1799,1835,1850,1854,1859,1875,1879,1892,1896,1907,1928,1933,1938,1943,1954,1959,1964,1968,1972,1977,1988,1998,2006,2010,2014,2039,2050,2059],{"__ignoreMap":43},[47,1772,1773,1775,1778,1780,1783,1785,1787,1789,1791,1793,1795,1797],{"class":49,"line":50},[47,1774,206],{"class":53},[47,1776,1777],{"class":57}," loggingMiddleware",[47,1779,74],{"class":64},[47,1781,1782],{"class":212},"next",[47,1784,229],{"class":57},[47,1786,232],{"class":64},[47,1788,1726],{"class":57},[47,1790,219],{"class":64},[47,1792,245],{"class":57},[47,1794,232],{"class":64},[47,1796,1726],{"class":57},[47,1798,65],{"class":64},[47,1800,1801,1803,1805,1807,1809,1811,1813,1815,1817,1819,1821,1823,1825,1827,1829,1831,1833],{"class":49,"line":68},[47,1802,1346],{"class":53},[47,1804,525],{"class":64},[47,1806,385],{"class":57},[47,1808,74],{"class":64},[47,1810,206],{"class":53},[47,1812,74],{"class":64},[47,1814,226],{"class":212},[47,1816,229],{"class":57},[47,1818,232],{"class":64},[47,1820,77],{"class":57},[47,1822,80],{"class":64},[47,1824,239],{"class":212},[47,1826,242],{"class":53},[47,1828,245],{"class":57},[47,1830,232],{"class":64},[47,1832,86],{"class":57},[47,1834,252],{"class":64},[47,1836,1837,1840,1842,1845,1848],{"class":49,"line":92},[47,1838,1839],{"class":64},"        start ",[47,1841,299],{"class":53},[47,1843,1844],{"class":64}," time.",[47,1846,1847],{"class":57},"Now",[47,1849,531],{"class":64},[47,1851,1852],{"class":49,"line":136},[47,1853,140],{"emptyLinePlaceholder":139},[47,1855,1856],{"class":49,"line":143},[47,1857,1858],{"class":119},"        \u002F\u002F Оборачиваем ResponseWriter чтобы перехватить статус код\n",[47,1860,1861,1864,1866,1869,1872],{"class":49,"line":152},[47,1862,1863],{"class":64},"        wrapped ",[47,1865,299],{"class":53},[47,1867,1868],{"class":53}," &",[47,1870,1871],{"class":57},"responseWriter",[47,1873,1874],{"class":64},"{ResponseWriter: w, status: http.StatusOK}\n",[47,1876,1877],{"class":49,"line":165},[47,1878,140],{"emptyLinePlaceholder":139},[47,1880,1881,1884,1886,1889],{"class":49,"line":174},[47,1882,1883],{"class":64},"        next.",[47,1885,104],{"class":57},[47,1887,1888],{"class":64},"(wrapped, r) ",[47,1890,1891],{"class":119},"\u002F\u002F вызываем следующий обработчик\n",[47,1893,1894],{"class":49,"line":179},[47,1895,140],{"emptyLinePlaceholder":139},[47,1897,1898,1901,1904],{"class":49,"line":184},[47,1899,1900],{"class":64},"        log.",[47,1902,1903],{"class":57},"Printf",[47,1905,1906],{"class":64},"(\n",[47,1908,1909,1912,1914,1917,1920,1923,1925],{"class":49,"line":198},[47,1910,1911],{"class":155},"            \"",[47,1913,794],{"class":793},[47,1915,1916],{"class":793}," %s",[47,1918,1919],{"class":793}," %d",[47,1921,1922],{"class":793}," %v",[47,1924,797],{"class":155},[47,1926,1927],{"class":64},",\n",[47,1929,1930],{"class":49,"line":203},[47,1931,1932],{"class":64},"            r.Method,\n",[47,1934,1935],{"class":49,"line":255},[47,1936,1937],{"class":64},"            r.URL.Path,\n",[47,1939,1940],{"class":49,"line":272},[47,1941,1942],{"class":64},"            wrapped.status,\n",[47,1944,1945,1948,1951],{"class":49,"line":277},[47,1946,1947],{"class":64},"            time.",[47,1949,1950],{"class":57},"Since",[47,1952,1953],{"class":64},"(start),\n",[47,1955,1956],{"class":49,"line":282},[47,1957,1958],{"class":64},"        )\n",[47,1960,1961],{"class":49,"line":293},[47,1962,1963],{"class":64},"    })\n",[47,1965,1966],{"class":49,"line":306},[47,1967,95],{"class":64},[47,1969,1970],{"class":49,"line":323},[47,1971,140],{"emptyLinePlaceholder":139},[47,1973,1974],{"class":49,"line":1045},[47,1975,1976],{"class":119},"\u002F\u002F Обёртка для перехвата статус кода\n",[47,1978,1979,1981,1984,1986],{"class":49,"line":1051},[47,1980,54],{"class":53},[47,1982,1983],{"class":57}," responseWriter",[47,1985,192],{"class":53},[47,1987,65],{"class":64},[47,1989,1990,1993,1995],{"class":49,"line":1066},[47,1991,1992],{"class":57},"    http",[47,1994,232],{"class":64},[47,1996,1997],{"class":57},"ResponseWriter\n",[47,1999,2000,2003],{"class":49,"line":1083},[47,2001,2002],{"class":64},"    status ",[47,2004,2005],{"class":53},"int\n",[47,2007,2008],{"class":49,"line":1583},[47,2009,95],{"class":64},[47,2011,2012],{"class":49,"line":1613},[47,2013,140],{"emptyLinePlaceholder":139},[47,2015,2016,2018,2020,2023,2025,2027,2029,2031,2033,2035,2037],{"class":49,"line":1627},[47,2017,206],{"class":53},[47,2019,209],{"class":64},[47,2021,2022],{"class":212},"rw ",[47,2024,83],{"class":53},[47,2026,1871],{"class":57},[47,2028,219],{"class":64},[47,2030,1191],{"class":57},[47,2032,74],{"class":64},[47,2034,1390],{"class":212},[47,2036,1393],{"class":53},[47,2038,252],{"class":64},[47,2040,2041,2044,2047],{"class":49,"line":1633},[47,2042,2043],{"class":64},"    rw.status ",[47,2045,2046],{"class":53},"=",[47,2048,2049],{"class":64}," status\n",[47,2051,2052,2055,2057],{"class":49,"line":1639},[47,2053,2054],{"class":64},"    rw.ResponseWriter.",[47,2056,1191],{"class":57},[47,2058,1432],{"class":64},[47,2060,2061],{"class":49,"line":1644},[47,2062,95],{"class":64},[327,2064,2066],{"id":2065},"аутентификация","Аутентификация",[38,2068,2070],{"className":40,"code":2069,"language":42,"meta":43,"style":43},"func authMiddleware(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        token := r.Header.Get(\"Authorization\")\n        if token == \"\" {\n            writeError(w, http.StatusUnauthorized, \"missing token\")\n            return \u002F\u002F не вызываем next\n        }\n\n        userID, err := validateToken(token)\n        if err != nil {\n            writeError(w, http.StatusUnauthorized, \"invalid token\")\n            return\n        }\n\n        \u002F\u002F Передаём userID в контекст\n        ctx := context.WithValue(r.Context(), userIDKey, userID)\n        next.ServeHTTP(w, r.WithContext(ctx))\n    })\n}\n",[19,2071,2072,2099,2135,2152,2168,2181,2189,2194,2198,2211,2223,2234,2239,2243,2247,2252,2272,2287,2291],{"__ignoreMap":43},[47,2073,2074,2076,2079,2081,2083,2085,2087,2089,2091,2093,2095,2097],{"class":49,"line":50},[47,2075,206],{"class":53},[47,2077,2078],{"class":57}," authMiddleware",[47,2080,74],{"class":64},[47,2082,1782],{"class":212},[47,2084,229],{"class":57},[47,2086,232],{"class":64},[47,2088,1726],{"class":57},[47,2090,219],{"class":64},[47,2092,245],{"class":57},[47,2094,232],{"class":64},[47,2096,1726],{"class":57},[47,2098,65],{"class":64},[47,2100,2101,2103,2105,2107,2109,2111,2113,2115,2117,2119,2121,2123,2125,2127,2129,2131,2133],{"class":49,"line":68},[47,2102,1346],{"class":53},[47,2104,525],{"class":64},[47,2106,385],{"class":57},[47,2108,74],{"class":64},[47,2110,206],{"class":53},[47,2112,74],{"class":64},[47,2114,226],{"class":212},[47,2116,229],{"class":57},[47,2118,232],{"class":64},[47,2120,77],{"class":57},[47,2122,80],{"class":64},[47,2124,239],{"class":212},[47,2126,242],{"class":53},[47,2128,245],{"class":57},[47,2130,232],{"class":64},[47,2132,86],{"class":57},[47,2134,252],{"class":64},[47,2136,2137,2140,2142,2144,2146,2148,2150],{"class":49,"line":92},[47,2138,2139],{"class":64},"        token ",[47,2141,299],{"class":53},[47,2143,976],{"class":64},[47,2145,928],{"class":57},[47,2147,74],{"class":64},[47,2149,983],{"class":155},[47,2151,89],{"class":64},[47,2153,2154,2157,2160,2163,2166],{"class":49,"line":136},[47,2155,2156],{"class":53},"        if",[47,2158,2159],{"class":64}," token ",[47,2161,2162],{"class":53},"==",[47,2164,2165],{"class":155}," \"\"",[47,2167,65],{"class":64},[47,2169,2170,2173,2176,2179],{"class":49,"line":143},[47,2171,2172],{"class":57},"            writeError",[47,2174,2175],{"class":64},"(w, http.StatusUnauthorized, ",[47,2177,2178],{"class":155},"\"missing token\"",[47,2180,89],{"class":64},[47,2182,2183,2186],{"class":49,"line":152},[47,2184,2185],{"class":53},"            return",[47,2187,2188],{"class":119}," \u002F\u002F не вызываем next\n",[47,2190,2191],{"class":49,"line":165},[47,2192,2193],{"class":64},"        }\n",[47,2195,2196],{"class":49,"line":174},[47,2197,140],{"emptyLinePlaceholder":139},[47,2199,2200,2203,2205,2208],{"class":49,"line":179},[47,2201,2202],{"class":64},"        userID, err ",[47,2204,299],{"class":53},[47,2206,2207],{"class":57}," validateToken",[47,2209,2210],{"class":64},"(token)\n",[47,2212,2213,2215,2217,2219,2221],{"class":49,"line":184},[47,2214,2156],{"class":53},[47,2216,1589],{"class":64},[47,2218,1605],{"class":53},[47,2220,1608],{"class":793},[47,2222,65],{"class":64},[47,2224,2225,2227,2229,2232],{"class":49,"line":198},[47,2226,2172],{"class":57},[47,2228,2175],{"class":64},[47,2230,2231],{"class":155},"\"invalid token\"",[47,2233,89],{"class":64},[47,2235,2236],{"class":49,"line":203},[47,2237,2238],{"class":53},"            return\n",[47,2240,2241],{"class":49,"line":255},[47,2242,2193],{"class":64},[47,2244,2245],{"class":49,"line":272},[47,2246,140],{"emptyLinePlaceholder":139},[47,2248,2249],{"class":49,"line":277},[47,2250,2251],{"class":119},"        \u002F\u002F Передаём userID в контекст\n",[47,2253,2254,2257,2259,2262,2265,2267,2269],{"class":49,"line":282},[47,2255,2256],{"class":64},"        ctx ",[47,2258,299],{"class":53},[47,2260,2261],{"class":64}," context.",[47,2263,2264],{"class":57},"WithValue",[47,2266,1655],{"class":64},[47,2268,1061],{"class":57},[47,2270,2271],{"class":64},"(), userIDKey, userID)\n",[47,2273,2274,2276,2278,2281,2284],{"class":49,"line":293},[47,2275,1883],{"class":64},[47,2277,104],{"class":57},[47,2279,2280],{"class":64},"(w, r.",[47,2282,2283],{"class":57},"WithContext",[47,2285,2286],{"class":64},"(ctx))\n",[47,2288,2289],{"class":49,"line":306},[47,2290,1963],{"class":64},[47,2292,2293],{"class":49,"line":323},[47,2294,95],{"class":64},[327,2296,2298],{"id":2297},"cors","CORS",[38,2300,2302],{"className":40,"code":2301,"language":42,"meta":43,"style":43},"func corsMiddleware(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        w.Header().Set(\"Access-Control-Allow-Origin\", \"*\")\n        w.Header().Set(\"Access-Control-Allow-Methods\", \"GET, POST, PUT, DELETE, OPTIONS\")\n        w.Header().Set(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization\")\n\n        if r.Method == http.MethodOptions {\n            w.WriteHeader(http.StatusNoContent)\n            return\n        }\n\n        next.ServeHTTP(w, r)\n    })\n}\n",[19,2303,2304,2331,2367,2390,2412,2434,2438,2450,2460,2464,2468,2472,2480,2484],{"__ignoreMap":43},[47,2305,2306,2308,2311,2313,2315,2317,2319,2321,2323,2325,2327,2329],{"class":49,"line":50},[47,2307,206],{"class":53},[47,2309,2310],{"class":57}," corsMiddleware",[47,2312,74],{"class":64},[47,2314,1782],{"class":212},[47,2316,229],{"class":57},[47,2318,232],{"class":64},[47,2320,1726],{"class":57},[47,2322,219],{"class":64},[47,2324,245],{"class":57},[47,2326,232],{"class":64},[47,2328,1726],{"class":57},[47,2330,65],{"class":64},[47,2332,2333,2335,2337,2339,2341,2343,2345,2347,2349,2351,2353,2355,2357,2359,2361,2363,2365],{"class":49,"line":68},[47,2334,1346],{"class":53},[47,2336,525],{"class":64},[47,2338,385],{"class":57},[47,2340,74],{"class":64},[47,2342,206],{"class":53},[47,2344,74],{"class":64},[47,2346,226],{"class":212},[47,2348,229],{"class":57},[47,2350,232],{"class":64},[47,2352,77],{"class":57},[47,2354,80],{"class":64},[47,2356,239],{"class":212},[47,2358,242],{"class":53},[47,2360,245],{"class":57},[47,2362,232],{"class":64},[47,2364,86],{"class":57},[47,2366,252],{"class":64},[47,2368,2369,2372,2374,2376,2378,2380,2383,2385,2388],{"class":49,"line":92},[47,2370,2371],{"class":64},"        w.",[47,2373,1137],{"class":57},[47,2375,925],{"class":64},[47,2377,1142],{"class":57},[47,2379,74],{"class":64},[47,2381,2382],{"class":155},"\"Access-Control-Allow-Origin\"",[47,2384,80],{"class":64},[47,2386,2387],{"class":155},"\"*\"",[47,2389,89],{"class":64},[47,2391,2392,2394,2396,2398,2400,2402,2405,2407,2410],{"class":49,"line":136},[47,2393,2371],{"class":64},[47,2395,1137],{"class":57},[47,2397,925],{"class":64},[47,2399,1142],{"class":57},[47,2401,74],{"class":64},[47,2403,2404],{"class":155},"\"Access-Control-Allow-Methods\"",[47,2406,80],{"class":64},[47,2408,2409],{"class":155},"\"GET, POST, PUT, DELETE, OPTIONS\"",[47,2411,89],{"class":64},[47,2413,2414,2416,2418,2420,2422,2424,2427,2429,2432],{"class":49,"line":143},[47,2415,2371],{"class":64},[47,2417,1137],{"class":57},[47,2419,925],{"class":64},[47,2421,1142],{"class":57},[47,2423,74],{"class":64},[47,2425,2426],{"class":155},"\"Access-Control-Allow-Headers\"",[47,2428,80],{"class":64},[47,2430,2431],{"class":155},"\"Content-Type, Authorization\"",[47,2433,89],{"class":64},[47,2435,2436],{"class":49,"line":152},[47,2437,140],{"emptyLinePlaceholder":139},[47,2439,2440,2442,2445,2447],{"class":49,"line":165},[47,2441,2156],{"class":53},[47,2443,2444],{"class":64}," r.Method ",[47,2446,2162],{"class":53},[47,2448,2449],{"class":64}," http.MethodOptions {\n",[47,2451,2452,2455,2457],{"class":49,"line":174},[47,2453,2454],{"class":64},"            w.",[47,2456,1191],{"class":57},[47,2458,2459],{"class":64},"(http.StatusNoContent)\n",[47,2461,2462],{"class":49,"line":179},[47,2463,2238],{"class":53},[47,2465,2466],{"class":49,"line":184},[47,2467,2193],{"class":64},[47,2469,2470],{"class":49,"line":198},[47,2471,140],{"emptyLinePlaceholder":139},[47,2473,2474,2476,2478],{"class":49,"line":203},[47,2475,1883],{"class":64},[47,2477,104],{"class":57},[47,2479,414],{"class":64},[47,2481,2482],{"class":49,"line":255},[47,2483,1963],{"class":64},[47,2485,2486],{"class":49,"line":272},[47,2487,95],{"class":64},[327,2489,2491],{"id":2490},"цепочка-middleware","Цепочка middleware",[38,2493,2495],{"className":40,"code":2494,"language":42,"meta":43,"style":43},"\u002F\u002F Применяем middleware справа налево — первый в списке выполняется первым\nfunc chain(h http.Handler, middlewares ...Middleware) http.Handler {\n    for i := len(middlewares) - 1; i >= 0; i-- {\n        h = middlewares[i](h)\n    }\n    return h\n}\n\n\u002F\u002F Использование\nmux := http.NewServeMux()\nmux.HandleFunc(\"GET \u002Fusers\", listUsers)\nmux.HandleFunc(\"POST \u002Fusers\", createUser)\n\nhandler := chain(mux,\n    loggingMiddleware,  \u002F\u002F выполняется первым\n    corsMiddleware,     \u002F\u002F вторым\n    authMiddleware,     \u002F\u002F третьим\n)\n\nhttp.ListenAndServe(\":8080\", handler)\n",[19,2496,2497,2502,2540,2579,2597,2601,2608,2612,2616,2620,2632,2644,2656,2660,2672,2680,2688,2696,2700,2704],{"__ignoreMap":43},[47,2498,2499],{"class":49,"line":50},[47,2500,2501],{"class":119},"\u002F\u002F Применяем middleware справа налево — первый в списке выполняется первым\n",[47,2503,2504,2506,2509,2511,2514,2516,2518,2520,2522,2525,2528,2530,2532,2534,2536,2538],{"class":49,"line":68},[47,2505,206],{"class":53},[47,2507,2508],{"class":57}," chain",[47,2510,74],{"class":64},[47,2512,2513],{"class":212},"h",[47,2515,229],{"class":57},[47,2517,232],{"class":64},[47,2519,1726],{"class":57},[47,2521,80],{"class":64},[47,2523,2524],{"class":212},"middlewares",[47,2526,2527],{"class":53}," ...",[47,2529,1720],{"class":57},[47,2531,219],{"class":64},[47,2533,245],{"class":57},[47,2535,232],{"class":64},[47,2537,1726],{"class":57},[47,2539,65],{"class":64},[47,2541,2542,2545,2548,2550,2553,2556,2559,2562,2565,2568,2571,2574,2577],{"class":49,"line":92},[47,2543,2544],{"class":53},"    for",[47,2546,2547],{"class":64}," i ",[47,2549,299],{"class":53},[47,2551,2552],{"class":57}," len",[47,2554,2555],{"class":64},"(middlewares) ",[47,2557,2558],{"class":53},"-",[47,2560,2561],{"class":793}," 1",[47,2563,2564],{"class":64},"; i ",[47,2566,2567],{"class":53},">=",[47,2569,2570],{"class":793}," 0",[47,2572,2573],{"class":64},"; i",[47,2575,2576],{"class":53},"--",[47,2578,65],{"class":64},[47,2580,2581,2584,2586,2589,2591,2594],{"class":49,"line":136},[47,2582,2583],{"class":64},"        h ",[47,2585,2046],{"class":53},[47,2587,2588],{"class":57}," middlewares",[47,2590,1509],{"class":64},[47,2592,2593],{"class":57},"i",[47,2595,2596],{"class":64},"](h)\n",[47,2598,2599],{"class":49,"line":143},[47,2600,1636],{"class":64},[47,2602,2603,2605],{"class":49,"line":152},[47,2604,1346],{"class":53},[47,2606,2607],{"class":64}," h\n",[47,2609,2610],{"class":49,"line":165},[47,2611,95],{"class":64},[47,2613,2614],{"class":49,"line":174},[47,2615,140],{"emptyLinePlaceholder":139},[47,2617,2618],{"class":49,"line":179},[47,2619,427],{"class":119},[47,2621,2622,2624,2626,2628,2630],{"class":49,"line":184},[47,2623,520],{"class":64},[47,2625,299],{"class":53},[47,2627,525],{"class":64},[47,2629,528],{"class":57},[47,2631,531],{"class":64},[47,2633,2634,2636,2638,2640,2642],{"class":49,"line":198},[47,2635,540],{"class":64},[47,2637,543],{"class":57},[47,2639,74],{"class":64},[47,2641,647],{"class":155},[47,2643,650],{"class":64},[47,2645,2646,2648,2650,2652,2654],{"class":49,"line":203},[47,2647,540],{"class":64},[47,2649,543],{"class":57},[47,2651,74],{"class":64},[47,2653,661],{"class":155},[47,2655,664],{"class":64},[47,2657,2658],{"class":49,"line":255},[47,2659,140],{"emptyLinePlaceholder":139},[47,2661,2662,2665,2667,2669],{"class":49,"line":272},[47,2663,2664],{"class":64},"handler ",[47,2666,299],{"class":53},[47,2668,2508],{"class":57},[47,2670,2671],{"class":64},"(mux,\n",[47,2673,2674,2677],{"class":49,"line":277},[47,2675,2676],{"class":64},"    loggingMiddleware,  ",[47,2678,2679],{"class":119},"\u002F\u002F выполняется первым\n",[47,2681,2682,2685],{"class":49,"line":282},[47,2683,2684],{"class":64},"    corsMiddleware,     ",[47,2686,2687],{"class":119},"\u002F\u002F вторым\n",[47,2689,2690,2693],{"class":49,"line":293},[47,2691,2692],{"class":64},"    authMiddleware,     ",[47,2694,2695],{"class":119},"\u002F\u002F третьим\n",[47,2697,2698],{"class":49,"line":306},[47,2699,89],{"class":64},[47,2701,2702],{"class":49,"line":323},[47,2703,140],{"emptyLinePlaceholder":139},[47,2705,2706,2708,2710,2712,2714],{"class":49,"line":1045},[47,2707,484],{"class":64},[47,2709,312],{"class":57},[47,2711,74],{"class":64},[47,2713,317],{"class":155},[47,2715,320],{"class":64},[27,2717],{},[30,2719,2721],{"id":2720},"httpclient-выполнение-запросов","http.Client — выполнение запросов",[16,2723,2724,2726],{},[19,2725,21],{}," — не только сервер, но и клиент для выполнения HTTP-запросов:",[38,2728,2730],{"className":40,"code":2729,"language":42,"meta":43,"style":43},"\u002F\u002F Никогда не используйте http.DefaultClient в production!\n\u002F\u002F У него нет таймаутов — соединение может висеть вечно\nclient := &http.Client{\n    Timeout: 10 * time.Second, \u002F\u002F общий таймаут запроса\n    Transport: &http.Transport{\n        MaxIdleConns:        100,\n        MaxIdleConnsPerHost: 10,\n        IdleConnTimeout:     90 * time.Second,\n    },\n}\n\n\u002F\u002F GET запрос\nfunc getUser(ctx context.Context, id int) (*User, error) {\n    url := fmt.Sprintf(\"https:\u002F\u002Fapi.example.com\u002Fusers\u002F%d\", id)\n    req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n    if err != nil {\n        return nil, fmt.Errorf(\"create request: %w\", err)\n    }\n    req.Header.Set(\"Authorization\", \"Bearer \"+token)\n\n    resp, err := client.Do(req)\n    if err != nil {\n        return nil, fmt.Errorf(\"do request: %w\", err)\n    }\n    defer resp.Body.Close()\n\n    if resp.StatusCode != http.StatusOK {\n        return nil, fmt.Errorf(\"unexpected status: %d\", resp.StatusCode)\n    }\n\n    var user User\n    if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {\n        return nil, fmt.Errorf(\"decode response: %w\", err)\n    }\n\n    return &user, nil\n}\n",[19,2731,2732,2737,2742,2761,2777,2793,2803,2812,2825,2830,2834,2838,2843,2882,2907,2927,2939,2965,2969,2991,2995,3011,3023,3044,3048,3059,3063,3075,3097,3101,3105,3115,3145,3166,3170,3174,3186],{"__ignoreMap":43},[47,2733,2734],{"class":49,"line":50},[47,2735,2736],{"class":119},"\u002F\u002F Никогда не используйте http.DefaultClient в production!\n",[47,2738,2739],{"class":49,"line":68},[47,2740,2741],{"class":119},"\u002F\u002F У него нет таймаутов — соединение может висеть вечно\n",[47,2743,2744,2747,2749,2751,2753,2755,2758],{"class":49,"line":92},[47,2745,2746],{"class":64},"client ",[47,2748,299],{"class":53},[47,2750,1868],{"class":53},[47,2752,245],{"class":57},[47,2754,232],{"class":64},[47,2756,2757],{"class":57},"Client",[47,2759,2760],{"class":64},"{\n",[47,2762,2763,2766,2769,2771,2774],{"class":49,"line":136},[47,2764,2765],{"class":64},"    Timeout: ",[47,2767,2768],{"class":793},"10",[47,2770,242],{"class":53},[47,2772,2773],{"class":64}," time.Second, ",[47,2775,2776],{"class":119},"\u002F\u002F общий таймаут запроса\n",[47,2778,2779,2782,2784,2786,2788,2791],{"class":49,"line":143},[47,2780,2781],{"class":64},"    Transport: ",[47,2783,1599],{"class":53},[47,2785,245],{"class":57},[47,2787,232],{"class":64},[47,2789,2790],{"class":57},"Transport",[47,2792,2760],{"class":64},[47,2794,2795,2798,2801],{"class":49,"line":152},[47,2796,2797],{"class":64},"        MaxIdleConns:        ",[47,2799,2800],{"class":793},"100",[47,2802,1927],{"class":64},[47,2804,2805,2808,2810],{"class":49,"line":165},[47,2806,2807],{"class":64},"        MaxIdleConnsPerHost: ",[47,2809,2768],{"class":793},[47,2811,1927],{"class":64},[47,2813,2814,2817,2820,2822],{"class":49,"line":174},[47,2815,2816],{"class":64},"        IdleConnTimeout:     ",[47,2818,2819],{"class":793},"90",[47,2821,242],{"class":53},[47,2823,2824],{"class":64}," time.Second,\n",[47,2826,2827],{"class":49,"line":179},[47,2828,2829],{"class":64},"    },\n",[47,2831,2832],{"class":49,"line":184},[47,2833,95],{"class":64},[47,2835,2836],{"class":49,"line":198},[47,2837,140],{"emptyLinePlaceholder":139},[47,2839,2840],{"class":49,"line":203},[47,2841,2842],{"class":119},"\u002F\u002F GET запрос\n",[47,2844,2845,2847,2849,2851,2854,2857,2859,2861,2863,2866,2868,2871,2873,2876,2878,2880],{"class":49,"line":255},[47,2846,206],{"class":53},[47,2848,731],{"class":57},[47,2850,74],{"class":64},[47,2852,2853],{"class":212},"ctx",[47,2855,2856],{"class":57}," context",[47,2858,232],{"class":64},[47,2860,1061],{"class":57},[47,2862,80],{"class":64},[47,2864,2865],{"class":212},"id",[47,2867,1393],{"class":53},[47,2869,2870],{"class":64},") (",[47,2872,83],{"class":53},[47,2874,2875],{"class":57},"User",[47,2877,80],{"class":64},[47,2879,1297],{"class":53},[47,2881,252],{"class":64},[47,2883,2884,2887,2889,2892,2895,2897,2900,2903,2905],{"class":49,"line":272},[47,2885,2886],{"class":64},"    url ",[47,2888,299],{"class":53},[47,2890,2891],{"class":64}," fmt.",[47,2893,2894],{"class":57},"Sprintf",[47,2896,74],{"class":64},[47,2898,2899],{"class":155},"\"https:\u002F\u002Fapi.example.com\u002Fusers\u002F",[47,2901,2902],{"class":793},"%d",[47,2904,797],{"class":155},[47,2906,800],{"class":64},[47,2908,2909,2912,2914,2916,2919,2922,2925],{"class":49,"line":277},[47,2910,2911],{"class":64},"    req, err ",[47,2913,299],{"class":53},[47,2915,525],{"class":64},[47,2917,2918],{"class":57},"NewRequestWithContext",[47,2920,2921],{"class":64},"(ctx, http.MethodGet, url, ",[47,2923,2924],{"class":793},"nil",[47,2926,89],{"class":64},[47,2928,2929,2931,2933,2935,2937],{"class":49,"line":282},[47,2930,1586],{"class":53},[47,2932,1589],{"class":64},[47,2934,1605],{"class":53},[47,2936,1608],{"class":793},[47,2938,65],{"class":64},[47,2940,2941,2944,2946,2949,2952,2954,2957,2960,2962],{"class":49,"line":293},[47,2942,2943],{"class":53},"        return",[47,2945,1608],{"class":793},[47,2947,2948],{"class":64},", fmt.",[47,2950,2951],{"class":57},"Errorf",[47,2953,74],{"class":64},[47,2955,2956],{"class":155},"\"create request: ",[47,2958,2959],{"class":793},"%w",[47,2961,797],{"class":155},[47,2963,2964],{"class":64},", err)\n",[47,2966,2967],{"class":49,"line":306},[47,2968,1636],{"class":64},[47,2970,2971,2974,2976,2978,2980,2982,2985,2988],{"class":49,"line":323},[47,2972,2973],{"class":64},"    req.Header.",[47,2975,1142],{"class":57},[47,2977,74],{"class":64},[47,2979,983],{"class":155},[47,2981,80],{"class":64},[47,2983,2984],{"class":155},"\"Bearer \"",[47,2986,2987],{"class":53},"+",[47,2989,2990],{"class":64},"token)\n",[47,2992,2993],{"class":49,"line":1045},[47,2994,140],{"emptyLinePlaceholder":139},[47,2996,2997,3000,3002,3005,3008],{"class":49,"line":1051},[47,2998,2999],{"class":64},"    resp, err ",[47,3001,299],{"class":53},[47,3003,3004],{"class":64}," client.",[47,3006,3007],{"class":57},"Do",[47,3009,3010],{"class":64},"(req)\n",[47,3012,3013,3015,3017,3019,3021],{"class":49,"line":1066},[47,3014,1586],{"class":53},[47,3016,1589],{"class":64},[47,3018,1605],{"class":53},[47,3020,1608],{"class":793},[47,3022,65],{"class":64},[47,3024,3025,3027,3029,3031,3033,3035,3038,3040,3042],{"class":49,"line":1083},[47,3026,2943],{"class":53},[47,3028,1608],{"class":793},[47,3030,2948],{"class":64},[47,3032,2951],{"class":57},[47,3034,74],{"class":64},[47,3036,3037],{"class":155},"\"do request: ",[47,3039,2959],{"class":793},[47,3041,797],{"class":155},[47,3043,2964],{"class":64},[47,3045,3046],{"class":49,"line":1583},[47,3047,1636],{"class":64},[47,3049,3050,3052,3055,3057],{"class":49,"line":1613},[47,3051,1304],{"class":53},[47,3053,3054],{"class":64}," resp.Body.",[47,3056,1310],{"class":57},[47,3058,531],{"class":64},[47,3060,3061],{"class":49,"line":1627},[47,3062,140],{"emptyLinePlaceholder":139},[47,3064,3065,3067,3070,3072],{"class":49,"line":1633},[47,3066,1586],{"class":53},[47,3068,3069],{"class":64}," resp.StatusCode ",[47,3071,1605],{"class":53},[47,3073,3074],{"class":64}," http.StatusOK {\n",[47,3076,3077,3079,3081,3083,3085,3087,3090,3092,3094],{"class":49,"line":1639},[47,3078,2943],{"class":53},[47,3080,1608],{"class":793},[47,3082,2948],{"class":64},[47,3084,2951],{"class":57},[47,3086,74],{"class":64},[47,3088,3089],{"class":155},"\"unexpected status: ",[47,3091,2902],{"class":793},[47,3093,797],{"class":155},[47,3095,3096],{"class":64},", resp.StatusCode)\n",[47,3098,3099],{"class":49,"line":1644},[47,3100,1636],{"class":64},[47,3102,3103],{"class":49,"line":1663},[47,3104,140],{"emptyLinePlaceholder":139},[47,3106,3107,3109,3112],{"class":49,"line":1676},[47,3108,1574],{"class":53},[47,3110,3111],{"class":64}," user ",[47,3113,3114],{"class":57},"User\n",[47,3116,3117,3119,3121,3123,3125,3127,3130,3132,3134,3136,3139,3141,3143],{"class":49,"line":1689},[47,3118,1586],{"class":53},[47,3120,1589],{"class":64},[47,3122,299],{"class":53},[47,3124,1322],{"class":64},[47,3126,1325],{"class":57},[47,3128,3129],{"class":64},"(resp.Body).",[47,3131,1352],{"class":57},[47,3133,74],{"class":64},[47,3135,1599],{"class":53},[47,3137,3138],{"class":64},"user); err ",[47,3140,1605],{"class":53},[47,3142,1608],{"class":793},[47,3144,65],{"class":64},[47,3146,3147,3149,3151,3153,3155,3157,3160,3162,3164],{"class":49,"line":1694},[47,3148,2943],{"class":53},[47,3150,1608],{"class":793},[47,3152,2948],{"class":64},[47,3154,2951],{"class":57},[47,3156,74],{"class":64},[47,3158,3159],{"class":155},"\"decode response: ",[47,3161,2959],{"class":793},[47,3163,797],{"class":155},[47,3165,2964],{"class":64},[47,3167,3168],{"class":49,"line":1699},[47,3169,1636],{"class":64},[47,3171,3172],{"class":49,"line":1704},[47,3173,140],{"emptyLinePlaceholder":139},[47,3175,3176,3178,3180,3183],{"class":49,"line":1712},[47,3177,1346],{"class":53},[47,3179,1868],{"class":53},[47,3181,3182],{"class":64},"user, ",[47,3184,3185],{"class":793},"nil\n",[47,3187,3189],{"class":49,"line":3188},37,[47,3190,95],{"class":64},[27,3192],{},[30,3194,3196],{"id":3195},"graceful-shutdown","Graceful Shutdown",[16,3198,3199],{},"Production-сервер должен корректно завершаться: дождаться активных запросов и только потом остановиться. Иначе клиенты получат обрыв соединения:",[38,3201,3203],{"className":40,"code":3202,"language":42,"meta":112,"style":43},"\u002F\u002F (запуск на локальной машине: go run main.go)\n\u002F\u002F Graceful Shutdown — корректное завершение сервера\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"log\"\n    \"net\u002Fhttp\"\n    \"os\"\n    \"os\u002Fsignal\"\n    \"syscall\"\n    \"time\"\n)\n\nfunc healthHandler(w http.ResponseWriter, r *http.Request) {\n    fmt.Fprintln(w, \"ok\")\n}\n\nfunc main() {\n\nmux := http.NewServeMux()\nmux.HandleFunc(\"GET \u002Fhealth\", healthHandler)\n\u002F\u002F ... другие маршруты\n\nserver := &http.Server{\n    Addr:         \":8080\",\n    Handler:      mux,\n    ReadTimeout:  5 * time.Second,  \u002F\u002F время на чтение запроса\n    WriteTimeout: 10 * time.Second, \u002F\u002F время на запись ответа\n    IdleTimeout:  120 * time.Second,\u002F\u002F keep-alive соединения\n}\n\n\u002F\u002F Запускаем сервер в горутине\ngo func() {\n    log.Println(\"server started on :8080\")\n    if err := server.ListenAndServe(); err != http.ErrServerClosed {\n        log.Fatalf(\"server error: %v\", err)\n    }\n}()\n\n\u002F\u002F Ждём сигнала завершения (Ctrl+C или kill)\nquit := make(chan os.Signal, 1)\nsignal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)\n\u003C-quit\n\nlog.Println(\"shutting down server...\")\n\n\u002F\u002F Даём 30 секунд на завершение активных запросов\nctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\ndefer cancel()\n\nif err := server.Shutdown(ctx); err != nil {\n    log.Fatalf(\"forced shutdown: %v\", err)\n}\n\nlog.Println(\"server stopped\")\n}\n",[19,3204,3205,3209,3214,3220,3224,3230,3239,3247,3256,3264,3273,3282,3291,3300,3304,3308,3339,3352,3356,3360,3368,3372,3384,3398,3403,3407,3425,3434,3439,3455,3469,3485,3489,3493,3498,3506,3520,3541,3561,3566,3572,3577,3583,3614,3626,3635,3640,3655,3660,3666,3696,3707,3712,3736,3754,3759,3764,3778],{"__ignoreMap":43},[47,3206,3207],{"class":49,"line":50},[47,3208,125],{"class":119},[47,3210,3211],{"class":49,"line":68},[47,3212,3213],{"class":119},"\u002F\u002F Graceful Shutdown — корректное завершение сервера\n",[47,3215,3216,3218],{"class":49,"line":92},[47,3217,130],{"class":53},[47,3219,133],{"class":57},[47,3221,3222],{"class":49,"line":136},[47,3223,140],{"emptyLinePlaceholder":139},[47,3225,3226,3228],{"class":49,"line":143},[47,3227,146],{"class":53},[47,3229,149],{"class":64},[47,3231,3232,3234,3237],{"class":49,"line":152},[47,3233,156],{"class":155},[47,3235,3236],{"class":57},"context",[47,3238,162],{"class":155},[47,3240,3241,3243,3245],{"class":49,"line":165},[47,3242,156],{"class":155},[47,3244,159],{"class":57},[47,3246,162],{"class":155},[47,3248,3249,3251,3254],{"class":49,"line":174},[47,3250,156],{"class":155},[47,3252,3253],{"class":57},"log",[47,3255,162],{"class":155},[47,3257,3258,3260,3262],{"class":49,"line":179},[47,3259,156],{"class":155},[47,3261,21],{"class":57},[47,3263,162],{"class":155},[47,3265,3266,3268,3271],{"class":49,"line":184},[47,3267,156],{"class":155},[47,3269,3270],{"class":57},"os",[47,3272,162],{"class":155},[47,3274,3275,3277,3280],{"class":49,"line":198},[47,3276,156],{"class":155},[47,3278,3279],{"class":57},"os\u002Fsignal",[47,3281,162],{"class":155},[47,3283,3284,3286,3289],{"class":49,"line":203},[47,3285,156],{"class":155},[47,3287,3288],{"class":57},"syscall",[47,3290,162],{"class":155},[47,3292,3293,3295,3298],{"class":49,"line":255},[47,3294,156],{"class":155},[47,3296,3297],{"class":57},"time",[47,3299,162],{"class":155},[47,3301,3302],{"class":49,"line":272},[47,3303,89],{"class":64},[47,3305,3306],{"class":49,"line":277},[47,3307,140],{"emptyLinePlaceholder":139},[47,3309,3310,3312,3315,3317,3319,3321,3323,3325,3327,3329,3331,3333,3335,3337],{"class":49,"line":282},[47,3311,206],{"class":53},[47,3313,3314],{"class":57}," healthHandler",[47,3316,74],{"class":64},[47,3318,226],{"class":212},[47,3320,229],{"class":57},[47,3322,232],{"class":64},[47,3324,77],{"class":57},[47,3326,80],{"class":64},[47,3328,239],{"class":212},[47,3330,242],{"class":53},[47,3332,245],{"class":57},[47,3334,232],{"class":64},[47,3336,86],{"class":57},[47,3338,252],{"class":64},[47,3340,3341,3343,3345,3347,3350],{"class":49,"line":293},[47,3342,258],{"class":64},[47,3344,261],{"class":57},[47,3346,264],{"class":64},[47,3348,3349],{"class":155},"\"ok\"",[47,3351,89],{"class":64},[47,3353,3354],{"class":49,"line":306},[47,3355,95],{"class":64},[47,3357,3358],{"class":49,"line":323},[47,3359,140],{"emptyLinePlaceholder":139},[47,3361,3362,3364,3366],{"class":49,"line":1045},[47,3363,206],{"class":53},[47,3365,287],{"class":57},[47,3367,290],{"class":64},[47,3369,3370],{"class":49,"line":1051},[47,3371,140],{"emptyLinePlaceholder":139},[47,3373,3374,3376,3378,3380,3382],{"class":49,"line":1066},[47,3375,520],{"class":64},[47,3377,299],{"class":53},[47,3379,525],{"class":64},[47,3381,528],{"class":57},[47,3383,531],{"class":64},[47,3385,3386,3388,3390,3392,3395],{"class":49,"line":1083},[47,3387,540],{"class":64},[47,3389,543],{"class":57},[47,3391,74],{"class":64},[47,3393,3394],{"class":155},"\"GET \u002Fhealth\"",[47,3396,3397],{"class":64},", healthHandler)\n",[47,3399,3400],{"class":49,"line":1583},[47,3401,3402],{"class":119},"\u002F\u002F ... другие маршруты\n",[47,3404,3405],{"class":49,"line":1613},[47,3406,140],{"emptyLinePlaceholder":139},[47,3408,3409,3412,3414,3416,3418,3420,3423],{"class":49,"line":1627},[47,3410,3411],{"class":64},"server ",[47,3413,299],{"class":53},[47,3415,1868],{"class":53},[47,3417,245],{"class":57},[47,3419,232],{"class":64},[47,3421,3422],{"class":57},"Server",[47,3424,2760],{"class":64},[47,3426,3427,3430,3432],{"class":49,"line":1633},[47,3428,3429],{"class":64},"    Addr:         ",[47,3431,317],{"class":155},[47,3433,1927],{"class":64},[47,3435,3436],{"class":49,"line":1639},[47,3437,3438],{"class":64},"    Handler:      mux,\n",[47,3440,3441,3444,3447,3449,3452],{"class":49,"line":1644},[47,3442,3443],{"class":64},"    ReadTimeout:  ",[47,3445,3446],{"class":793},"5",[47,3448,242],{"class":53},[47,3450,3451],{"class":64}," time.Second,  ",[47,3453,3454],{"class":119},"\u002F\u002F время на чтение запроса\n",[47,3456,3457,3460,3462,3464,3466],{"class":49,"line":1663},[47,3458,3459],{"class":64},"    WriteTimeout: ",[47,3461,2768],{"class":793},[47,3463,242],{"class":53},[47,3465,2773],{"class":64},[47,3467,3468],{"class":119},"\u002F\u002F время на запись ответа\n",[47,3470,3471,3474,3477,3479,3482],{"class":49,"line":1676},[47,3472,3473],{"class":64},"    IdleTimeout:  ",[47,3475,3476],{"class":793},"120",[47,3478,242],{"class":53},[47,3480,3481],{"class":64}," time.Second,",[47,3483,3484],{"class":119},"\u002F\u002F keep-alive соединения\n",[47,3486,3487],{"class":49,"line":1689},[47,3488,95],{"class":64},[47,3490,3491],{"class":49,"line":1694},[47,3492,140],{"emptyLinePlaceholder":139},[47,3494,3495],{"class":49,"line":1699},[47,3496,3497],{"class":119},"\u002F\u002F Запускаем сервер в горутине\n",[47,3499,3500,3502,3504],{"class":49,"line":1704},[47,3501,42],{"class":53},[47,3503,357],{"class":53},[47,3505,290],{"class":64},[47,3507,3508,3511,3513,3515,3518],{"class":49,"line":1712},[47,3509,3510],{"class":64},"    log.",[47,3512,865],{"class":57},[47,3514,74],{"class":64},[47,3516,3517],{"class":155},"\"server started on :8080\"",[47,3519,89],{"class":64},[47,3521,3522,3524,3526,3528,3531,3533,3536,3538],{"class":49,"line":3188},[47,3523,1586],{"class":53},[47,3525,1589],{"class":64},[47,3527,299],{"class":53},[47,3529,3530],{"class":64}," server.",[47,3532,312],{"class":57},[47,3534,3535],{"class":64},"(); err ",[47,3537,1605],{"class":53},[47,3539,3540],{"class":64}," http.ErrServerClosed {\n",[47,3542,3544,3546,3549,3551,3554,3557,3559],{"class":49,"line":3543},38,[47,3545,1900],{"class":64},[47,3547,3548],{"class":57},"Fatalf",[47,3550,74],{"class":64},[47,3552,3553],{"class":155},"\"server error: ",[47,3555,3556],{"class":793},"%v",[47,3558,797],{"class":155},[47,3560,2964],{"class":64},[47,3562,3564],{"class":49,"line":3563},39,[47,3565,1636],{"class":64},[47,3567,3569],{"class":49,"line":3568},40,[47,3570,3571],{"class":64},"}()\n",[47,3573,3575],{"class":49,"line":3574},41,[47,3576,140],{"emptyLinePlaceholder":139},[47,3578,3580],{"class":49,"line":3579},42,[47,3581,3582],{"class":119},"\u002F\u002F Ждём сигнала завершения (Ctrl+C или kill)\n",[47,3584,3586,3589,3591,3594,3596,3599,3602,3604,3607,3609,3612],{"class":49,"line":3585},43,[47,3587,3588],{"class":64},"quit ",[47,3590,299],{"class":53},[47,3592,3593],{"class":57}," make",[47,3595,74],{"class":64},[47,3597,3598],{"class":53},"chan",[47,3600,3601],{"class":57}," os",[47,3603,232],{"class":64},[47,3605,3606],{"class":57},"Signal",[47,3608,80],{"class":64},[47,3610,3611],{"class":793},"1",[47,3613,89],{"class":64},[47,3615,3617,3620,3623],{"class":49,"line":3616},44,[47,3618,3619],{"class":64},"signal.",[47,3621,3622],{"class":57},"Notify",[47,3624,3625],{"class":64},"(quit, syscall.SIGINT, syscall.SIGTERM)\n",[47,3627,3629,3632],{"class":49,"line":3628},45,[47,3630,3631],{"class":53},"\u003C-",[47,3633,3634],{"class":64},"quit\n",[47,3636,3638],{"class":49,"line":3637},46,[47,3639,140],{"emptyLinePlaceholder":139},[47,3641,3643,3646,3648,3650,3653],{"class":49,"line":3642},47,[47,3644,3645],{"class":64},"log.",[47,3647,865],{"class":57},[47,3649,74],{"class":64},[47,3651,3652],{"class":155},"\"shutting down server...\"",[47,3654,89],{"class":64},[47,3656,3658],{"class":49,"line":3657},48,[47,3659,140],{"emptyLinePlaceholder":139},[47,3661,3663],{"class":49,"line":3662},49,[47,3664,3665],{"class":119},"\u002F\u002F Даём 30 секунд на завершение активных запросов\n",[47,3667,3669,3672,3674,3676,3679,3682,3685,3688,3691,3693],{"class":49,"line":3668},50,[47,3670,3671],{"class":64},"ctx, cancel ",[47,3673,299],{"class":53},[47,3675,2261],{"class":64},[47,3677,3678],{"class":57},"WithTimeout",[47,3680,3681],{"class":64},"(context.",[47,3683,3684],{"class":57},"Background",[47,3686,3687],{"class":64},"(), ",[47,3689,3690],{"class":793},"30",[47,3692,83],{"class":53},[47,3694,3695],{"class":64},"time.Second)\n",[47,3697,3699,3702,3705],{"class":49,"line":3698},51,[47,3700,3701],{"class":53},"defer",[47,3703,3704],{"class":57}," cancel",[47,3706,531],{"class":64},[47,3708,3710],{"class":49,"line":3709},52,[47,3711,140],{"emptyLinePlaceholder":139},[47,3713,3715,3718,3720,3722,3724,3727,3730,3732,3734],{"class":49,"line":3714},53,[47,3716,3717],{"class":53},"if",[47,3719,1589],{"class":64},[47,3721,299],{"class":53},[47,3723,3530],{"class":64},[47,3725,3726],{"class":57},"Shutdown",[47,3728,3729],{"class":64},"(ctx); err ",[47,3731,1605],{"class":53},[47,3733,1608],{"class":793},[47,3735,65],{"class":64},[47,3737,3739,3741,3743,3745,3748,3750,3752],{"class":49,"line":3738},54,[47,3740,3510],{"class":64},[47,3742,3548],{"class":57},[47,3744,74],{"class":64},[47,3746,3747],{"class":155},"\"forced shutdown: ",[47,3749,3556],{"class":793},[47,3751,797],{"class":155},[47,3753,2964],{"class":64},[47,3755,3757],{"class":49,"line":3756},55,[47,3758,95],{"class":64},[47,3760,3762],{"class":49,"line":3761},56,[47,3763,140],{"emptyLinePlaceholder":139},[47,3765,3767,3769,3771,3773,3776],{"class":49,"line":3766},57,[47,3768,3645],{"class":64},[47,3770,865],{"class":57},[47,3772,74],{"class":64},[47,3774,3775],{"class":155},"\"server stopped\"",[47,3777,89],{"class":64},[47,3779,3781],{"class":49,"line":3780},58,[47,3782,95],{"class":64},[27,3784],{},[30,3786,3788],{"id":3787},"net-одним-абзацем","net — одним абзацем",[16,3790,3791,3793,3794,3797,3798,1730],{},[19,3792,21],{}," построен поверх пакета ",[19,3795,3796],{},"net",". Если нужно понять уровень абстракции — вот как выглядит TCP-сервер напрямую через ",[19,3799,3796],{},[38,3801,3803],{"className":40,"code":3802,"language":42,"meta":43,"style":43},"\u002F\u002F Вот что делает net\u002Fhttp под капотом (упрощённо)\nlistener, err := net.Listen(\"tcp\", \":8080\")\nfor {\n    conn, err := listener.Accept() \u002F\u002F принимаем соединение\n    go handleConn(conn)            \u002F\u002F обрабатываем в горутине\n}\n",[19,3804,3805,3810,3834,3841,3859,3873],{"__ignoreMap":43},[47,3806,3807],{"class":49,"line":50},[47,3808,3809],{"class":119},"\u002F\u002F Вот что делает net\u002Fhttp под капотом (упрощённо)\n",[47,3811,3812,3815,3817,3820,3823,3825,3828,3830,3832],{"class":49,"line":68},[47,3813,3814],{"class":64},"listener, err ",[47,3816,299],{"class":53},[47,3818,3819],{"class":64}," net.",[47,3821,3822],{"class":57},"Listen",[47,3824,74],{"class":64},[47,3826,3827],{"class":155},"\"tcp\"",[47,3829,80],{"class":64},[47,3831,317],{"class":155},[47,3833,89],{"class":64},[47,3835,3836,3839],{"class":49,"line":92},[47,3837,3838],{"class":53},"for",[47,3840,65],{"class":64},[47,3842,3843,3846,3848,3851,3854,3856],{"class":49,"line":136},[47,3844,3845],{"class":64},"    conn, err ",[47,3847,299],{"class":53},[47,3849,3850],{"class":64}," listener.",[47,3852,3853],{"class":57},"Accept",[47,3855,1338],{"class":64},[47,3857,3858],{"class":119},"\u002F\u002F принимаем соединение\n",[47,3860,3861,3864,3867,3870],{"class":49,"line":143},[47,3862,3863],{"class":53},"    go",[47,3865,3866],{"class":57}," handleConn",[47,3868,3869],{"class":64},"(conn)            ",[47,3871,3872],{"class":119},"\u002F\u002F обрабатываем в горутине\n",[47,3874,3875],{"class":49,"line":152},[47,3876,95],{"class":64},[16,3878,3879,3881,3882,3884],{},[19,3880,21],{}," добавляет поверх этого: парсинг HTTP-протокола, маршрутизацию, keep-alive соединения, TLS, и всё что мы разобрали выше. Напрямую с ",[19,3883,3796],{}," работают когда нужен кастомный протокол поверх TCP — это уже за пределами большинства backend-задач.",[27,3886],{},[30,3888,3890],{"id":3889},"вопросы-на-собеседовании","Вопросы на собеседовании",[16,3892,3893,3897,3900,3901,3904,3905,3907],{},[3894,3895,3896],"strong",{},"Q: Что такое http.Handler? Как реализовать свой?",[3898,3899],"br",{},"\nA: Интерфейс с одним методом ",[19,3902,3903],{},"ServeHTTP(ResponseWriter, *Request)",". Любой тип реализующий этот метод является HTTP-обработчиком. Удобная альтернатива — ",[19,3906,336],{},": адаптер превращающий обычную функцию в Handler через приведение типа.",[16,3909,3910,3913,3915,3916,3918,3919,3918,3921,3923,3924,3926],{},[3894,3911,3912],{},"Q: Какой порядок вызовов в ResponseWriter и почему он важен?",[3898,3914],{},"\nA: Сначала ",[19,3917,1237],{},", потом ",[19,3920,1241],{},[19,3922,1244],{},". После первого вызова ",[19,3925,1244],{}," заголовки и статус код уже отправлены клиенту — изменить их невозможно. Go выведет предупреждение в лог но не вернёт ошибку.",[16,3928,3929,3932,3934,3935,3938],{},[3894,3930,3931],{},"Q: Что такое middleware? Как реализовать цепочку?",[3898,3933],{},"\nA: Функция типа ",[19,3936,3937],{},"func(http.Handler) http.Handler"," — принимает обработчик и возвращает новый с дополнительным поведением. Цепочка строится последовательным оборачиванием: каждый middleware получает следующий как аргумент и вызывает его внутри своего ServeHTTP.",[16,3940,3941,3944,3946],{},[3894,3942,3943],{},"Q: Почему нельзя использовать http.DefaultClient в production?",[3898,3945],{},"\nA: У DefaultClient нет таймаутов — запрос к зависшему серверу будет висеть вечно, занимая горутину и соединение. В production всегда создают клиент с явными таймаутами и настроенным Transport.",[16,3948,3949,3952,3954,3955,3958],{},[3894,3950,3951],{},"Q: Что такое Graceful Shutdown и как реализовать?",[3898,3953],{},"\nA: Корректное завершение сервера: перестаём принимать новые соединения, ждём завершения активных запросов, потом останавливаемся. Реализуется через ",[19,3956,3957],{},"server.Shutdown(ctx)"," после получения OS-сигнала (SIGINT\u002FSIGTERM). Без этого активные запросы получат обрыв соединения.",[16,3960,3961,3964,3966,3967,3970,3971,3973,3974,3977],{},[3894,3962,3963],{},"Q: Что нового в маршрутизаторе Go 1.22?",[3898,3965],{},"\nA: Поддержка HTTP-методов в паттерне (",[19,3968,3969],{},"GET \u002Fusers","), path-параметры через фигурные скобки (",[19,3972,609],{},"), получение параметра через ",[19,3975,3976],{},"r.PathValue(\"id\")",". До 1.22 для этого требовались сторонние роутеры.",[16,3979,3980,3983,3985,3986,3989,3990,3993],{},[3894,3981,3982],{},"Q: Как передать данные из middleware в обработчик?",[3898,3984],{},"\nA: Через контекст запроса: ",[19,3987,3988],{},"r.WithContext(context.WithValue(r.Context(), key, value))",". Обработчик читает через ",[19,3991,3992],{},"r.Context().Value(key)",". Ключ должен быть собственного типа чтобы избежать коллизий.",[16,3995,3996],{},[3894,3997,3998,3999,4002],{},"Q: Нужно ли закрывать ",[19,4000,4001],{},"r.Body"," в серверном handler?",[16,4004,4005,4006,4008,4009,4012,4013,4016,4017,232],{},"A: Обычно нет: в server-side ",[19,4007,21],{}," тело входящего запроса закрывает сервер. В handler важно ",[3894,4010,4011],{},"дочитать или ограничить"," body, если нужно переиспользование соединения и защита от больших payload. Обязательное ",[19,4014,4015],{},"defer resp.Body.Close()"," относится к HTTP-клиенту после ",[19,4018,4019],{},"client.Do(req)",[27,4021],{},[27,4023],{},[30,4025,4027],{"id":4026},"практика","Практика",[4029,4030,4033,4040,4078],"quiz",{"answer":4031,"id":4032,"xp":2768},"2","web-http-q1",[16,4034,4035,4036,4039],{},"В каком порядке нужно формировать HTTP-ответ через ",[19,4037,4038],{},"http.ResponseWriter","?",[4041,4042,4043],"template",{"v-slot:options":43},[4044,4045,4046,4057,4067,4072],"ul",{},[4047,4048,4049,4050,3918,4052,3918,4055],"li",{},"Сначала ",[19,4051,1213],{},[19,4053,4054],{},"Header().Set",[19,4056,1191],{},[4047,4058,4059,4060,3918,4062,4064,4065],{},"Сначала заголовки через ",[19,4061,4054],{},[19,4063,1191],{},", потом тело через ",[19,4066,1213],{},[4047,4068,4049,4069,4071],{},[19,4070,1191],{},", потом можно менять заголовки сколько угодно",[4047,4073,4074,4075,4077],{},"Порядок не важен: ",[19,4076,77],{}," всё переупорядочит сам",[4041,4079,4080],{"v-slot:explanation":43},[16,4081,4082,4083,4085,4086,4088],{},"После первого ",[19,4084,1191],{}," или ",[19,4087,1213],{}," статус и заголовки считаются отправленными. Поэтому заголовки нужно выставить заранее, статус — до тела, а уже потом писать body.",[4090,4091,4095,4098,4349],"predict",{"answer":4092,"id":4093,"xp":4094},"201\\nok\\ntodo","web-http-p1","15",[16,4096,4097],{},"Что выведет программа?",[4041,4099,4100],{"v-slot:code":43},[38,4101,4103],{"className":40,"code":4102,"language":42,"meta":43,"style":43},"package main\n\nimport (\n    \"fmt\"\n    \"net\u002Fhttp\"\n    \"net\u002Fhttp\u002Fhttptest\"\n)\n\nfunc create(w http.ResponseWriter, r *http.Request) {\n    w.Header().Set(\"X-App\", \"todo\")\n    w.WriteHeader(http.StatusCreated)\n    fmt.Fprint(w, \"ok\")\n}\n\nfunc main() {\n    req := httptest.NewRequest(http.MethodPost, \"\u002Ftodos\", nil)\n    rec := httptest.NewRecorder()\n\n    create(rec, req)\n\n    fmt.Println(rec.Code)\n    fmt.Println(rec.Body.String())\n    fmt.Println(rec.Header().Get(\"X-App\"))\n}\n",[19,4104,4105,4111,4115,4121,4129,4137,4146,4150,4154,4185,4207,4216,4229,4233,4237,4245,4270,4284,4288,4296,4300,4309,4324,4345],{"__ignoreMap":43},[47,4106,4107,4109],{"class":49,"line":50},[47,4108,130],{"class":53},[47,4110,133],{"class":57},[47,4112,4113],{"class":49,"line":68},[47,4114,140],{"emptyLinePlaceholder":139},[47,4116,4117,4119],{"class":49,"line":92},[47,4118,146],{"class":53},[47,4120,149],{"class":64},[47,4122,4123,4125,4127],{"class":49,"line":136},[47,4124,156],{"class":155},[47,4126,159],{"class":57},[47,4128,162],{"class":155},[47,4130,4131,4133,4135],{"class":49,"line":143},[47,4132,156],{"class":155},[47,4134,21],{"class":57},[47,4136,162],{"class":155},[47,4138,4139,4141,4144],{"class":49,"line":152},[47,4140,156],{"class":155},[47,4142,4143],{"class":57},"net\u002Fhttp\u002Fhttptest",[47,4145,162],{"class":155},[47,4147,4148],{"class":49,"line":165},[47,4149,89],{"class":64},[47,4151,4152],{"class":49,"line":174},[47,4153,140],{"emptyLinePlaceholder":139},[47,4155,4156,4158,4161,4163,4165,4167,4169,4171,4173,4175,4177,4179,4181,4183],{"class":49,"line":179},[47,4157,206],{"class":53},[47,4159,4160],{"class":57}," create",[47,4162,74],{"class":64},[47,4164,226],{"class":212},[47,4166,229],{"class":57},[47,4168,232],{"class":64},[47,4170,77],{"class":57},[47,4172,80],{"class":64},[47,4174,239],{"class":212},[47,4176,242],{"class":53},[47,4178,245],{"class":57},[47,4180,232],{"class":64},[47,4182,86],{"class":57},[47,4184,252],{"class":64},[47,4186,4187,4189,4191,4193,4195,4197,4200,4202,4205],{"class":49,"line":184},[47,4188,1134],{"class":64},[47,4190,1137],{"class":57},[47,4192,925],{"class":64},[47,4194,1142],{"class":57},[47,4196,74],{"class":64},[47,4198,4199],{"class":155},"\"X-App\"",[47,4201,80],{"class":64},[47,4203,4204],{"class":155},"\"todo\"",[47,4206,89],{"class":64},[47,4208,4209,4211,4213],{"class":49,"line":198},[47,4210,1134],{"class":64},[47,4212,1191],{"class":57},[47,4214,4215],{"class":64},"(http.StatusCreated)\n",[47,4217,4218,4220,4223,4225,4227],{"class":49,"line":203},[47,4219,258],{"class":64},[47,4221,4222],{"class":57},"Fprint",[47,4224,264],{"class":64},[47,4226,3349],{"class":155},[47,4228,89],{"class":64},[47,4230,4231],{"class":49,"line":255},[47,4232,95],{"class":64},[47,4234,4235],{"class":49,"line":272},[47,4236,140],{"emptyLinePlaceholder":139},[47,4238,4239,4241,4243],{"class":49,"line":277},[47,4240,206],{"class":53},[47,4242,287],{"class":57},[47,4244,290],{"class":64},[47,4246,4247,4250,4252,4255,4258,4261,4264,4266,4268],{"class":49,"line":282},[47,4248,4249],{"class":64},"    req ",[47,4251,299],{"class":53},[47,4253,4254],{"class":64}," httptest.",[47,4256,4257],{"class":57},"NewRequest",[47,4259,4260],{"class":64},"(http.MethodPost, ",[47,4262,4263],{"class":155},"\"\u002Ftodos\"",[47,4265,80],{"class":64},[47,4267,2924],{"class":793},[47,4269,89],{"class":64},[47,4271,4272,4275,4277,4279,4282],{"class":49,"line":293},[47,4273,4274],{"class":64},"    rec ",[47,4276,299],{"class":53},[47,4278,4254],{"class":64},[47,4280,4281],{"class":57},"NewRecorder",[47,4283,531],{"class":64},[47,4285,4286],{"class":49,"line":306},[47,4287,140],{"emptyLinePlaceholder":139},[47,4289,4290,4293],{"class":49,"line":323},[47,4291,4292],{"class":57},"    create",[47,4294,4295],{"class":64},"(rec, req)\n",[47,4297,4298],{"class":49,"line":1045},[47,4299,140],{"emptyLinePlaceholder":139},[47,4301,4302,4304,4306],{"class":49,"line":1051},[47,4303,258],{"class":64},[47,4305,865],{"class":57},[47,4307,4308],{"class":64},"(rec.Code)\n",[47,4310,4311,4313,4315,4318,4321],{"class":49,"line":1066},[47,4312,258],{"class":64},[47,4314,865],{"class":57},[47,4316,4317],{"class":64},"(rec.Body.",[47,4319,4320],{"class":57},"String",[47,4322,4323],{"class":64},"())\n",[47,4325,4326,4328,4330,4333,4335,4337,4339,4341,4343],{"class":49,"line":1083},[47,4327,258],{"class":64},[47,4329,865],{"class":57},[47,4331,4332],{"class":64},"(rec.",[47,4334,1137],{"class":57},[47,4336,925],{"class":64},[47,4338,928],{"class":57},[47,4340,74],{"class":64},[47,4342,4199],{"class":155},[47,4344,1227],{"class":64},[47,4346,4347],{"class":49,"line":1583},[47,4348,95],{"class":64},[4041,4350,4351],{"v-slot:hint":43},[16,4352,4353,4356,4357,4360,4361,232],{},[19,4354,4355],{},"httptest.NewRecorder"," записывает статус, заголовки и body так, как их получил бы клиент. ",[19,4358,4359],{},"http.StatusCreated"," — это ",[19,4362,4363],{},"201",[4365,4366,4370,4384,4664],"code-task",{"expected":4367,"id":4368,"xp":4369},"201\\napplication\u002Fjson\\n1","web-http-ct1","20",[16,4371,4372,4373,4376,4377,4380,4381,4383],{},"Реализуй helper ",[19,4374,4375],{},"writeJSON",": он должен поставить ",[19,4378,4379],{},"Content-Type",", записать status code и закодировать ",[19,4382,1398],{}," в JSON.",[4041,4385,4386],{"v-slot:template":43},[38,4387,4389],{"className":40,"code":4388,"language":42,"meta":43,"style":43},"package main\n\nimport (\n    \"bytes\"\n    \"encoding\u002Fjson\"\n    \"fmt\"\n    \"net\u002Fhttp\"\n    \"net\u002Fhttp\u002Fhttptest\"\n)\n\nfunc writeJSON(w http.ResponseWriter, status int, data any) {\n}\n\nfunc main() {\n    rec := httptest.NewRecorder()\n    writeJSON(rec, http.StatusCreated, map[string]int{\"id\": 1})\n\n    fmt.Println(rec.Code)\n    fmt.Println(rec.Header().Get(\"Content-Type\"))\n\n    var payload map[string]int\n    _ = json.NewDecoder(bytes.NewReader(rec.Body.Bytes())).Decode(&payload)\n    fmt.Println(payload[\"id\"])\n\n    _ = json.NewEncoder\n}\n",[19,4390,4391,4397,4401,4407,4416,4425,4433,4441,4449,4453,4457,4488,4492,4496,4504,4516,4546,4550,4558,4578,4582,4599,4633,4647,4651,4660],{"__ignoreMap":43},[47,4392,4393,4395],{"class":49,"line":50},[47,4394,130],{"class":53},[47,4396,133],{"class":57},[47,4398,4399],{"class":49,"line":68},[47,4400,140],{"emptyLinePlaceholder":139},[47,4402,4403,4405],{"class":49,"line":92},[47,4404,146],{"class":53},[47,4406,149],{"class":64},[47,4408,4409,4411,4414],{"class":49,"line":136},[47,4410,156],{"class":155},[47,4412,4413],{"class":57},"bytes",[47,4415,162],{"class":155},[47,4417,4418,4420,4423],{"class":49,"line":143},[47,4419,156],{"class":155},[47,4421,4422],{"class":57},"encoding\u002Fjson",[47,4424,162],{"class":155},[47,4426,4427,4429,4431],{"class":49,"line":152},[47,4428,156],{"class":155},[47,4430,159],{"class":57},[47,4432,162],{"class":155},[47,4434,4435,4437,4439],{"class":49,"line":165},[47,4436,156],{"class":155},[47,4438,21],{"class":57},[47,4440,162],{"class":155},[47,4442,4443,4445,4447],{"class":49,"line":174},[47,4444,156],{"class":155},[47,4446,4143],{"class":57},[47,4448,162],{"class":155},[47,4450,4451],{"class":49,"line":179},[47,4452,89],{"class":64},[47,4454,4455],{"class":49,"line":184},[47,4456,140],{"emptyLinePlaceholder":139},[47,4458,4459,4461,4463,4465,4467,4469,4471,4473,4475,4477,4479,4481,4483,4486],{"class":49,"line":198},[47,4460,206],{"class":53},[47,4462,1375],{"class":57},[47,4464,74],{"class":64},[47,4466,226],{"class":212},[47,4468,229],{"class":57},[47,4470,232],{"class":64},[47,4472,77],{"class":57},[47,4474,80],{"class":64},[47,4476,1390],{"class":212},[47,4478,1393],{"class":53},[47,4480,80],{"class":64},[47,4482,1398],{"class":212},[47,4484,4485],{"class":57}," any",[47,4487,252],{"class":64},[47,4489,4490],{"class":49,"line":203},[47,4491,95],{"class":64},[47,4493,4494],{"class":49,"line":255},[47,4495,140],{"emptyLinePlaceholder":139},[47,4497,4498,4500,4502],{"class":49,"line":272},[47,4499,206],{"class":53},[47,4501,287],{"class":57},[47,4503,290],{"class":64},[47,4505,4506,4508,4510,4512,4514],{"class":49,"line":277},[47,4507,4274],{"class":64},[47,4509,299],{"class":53},[47,4511,4254],{"class":64},[47,4513,4281],{"class":57},[47,4515,531],{"class":64},[47,4517,4518,4520,4523,4525,4527,4529,4531,4534,4536,4538,4541,4543],{"class":49,"line":282},[47,4519,1500],{"class":57},[47,4521,4522],{"class":64},"(rec, http.StatusCreated, ",[47,4524,1506],{"class":53},[47,4526,1509],{"class":64},[47,4528,1512],{"class":53},[47,4530,1515],{"class":64},[47,4532,4533],{"class":53},"int",[47,4535,1520],{"class":64},[47,4537,773],{"class":155},[47,4539,4540],{"class":64},": ",[47,4542,3611],{"class":793},[47,4544,4545],{"class":64},"})\n",[47,4547,4548],{"class":49,"line":293},[47,4549,140],{"emptyLinePlaceholder":139},[47,4551,4552,4554,4556],{"class":49,"line":306},[47,4553,258],{"class":64},[47,4555,865],{"class":57},[47,4557,4308],{"class":64},[47,4559,4560,4562,4564,4566,4568,4570,4572,4574,4576],{"class":49,"line":323},[47,4561,258],{"class":64},[47,4563,865],{"class":57},[47,4565,4332],{"class":64},[47,4567,1137],{"class":57},[47,4569,925],{"class":64},[47,4571,928],{"class":57},[47,4573,74],{"class":64},[47,4575,1001],{"class":155},[47,4577,1227],{"class":64},[47,4579,4580],{"class":49,"line":1045},[47,4581,140],{"emptyLinePlaceholder":139},[47,4583,4584,4586,4589,4591,4593,4595,4597],{"class":49,"line":1051},[47,4585,1574],{"class":53},[47,4587,4588],{"class":64}," payload ",[47,4590,1506],{"class":53},[47,4592,1509],{"class":64},[47,4594,1512],{"class":53},[47,4596,1515],{"class":64},[47,4598,2005],{"class":53},[47,4600,4601,4604,4606,4608,4610,4613,4616,4618,4621,4624,4626,4628,4630],{"class":49,"line":1066},[47,4602,4603],{"class":64},"    _ ",[47,4605,2046],{"class":53},[47,4607,1322],{"class":64},[47,4609,1325],{"class":57},[47,4611,4612],{"class":64},"(bytes.",[47,4614,4615],{"class":57},"NewReader",[47,4617,4317],{"class":64},[47,4619,4620],{"class":57},"Bytes",[47,4622,4623],{"class":64},"())).",[47,4625,1352],{"class":57},[47,4627,74],{"class":64},[47,4629,1599],{"class":53},[47,4631,4632],{"class":64},"payload)\n",[47,4634,4635,4637,4639,4642,4644],{"class":49,"line":1083},[47,4636,258],{"class":64},[47,4638,865],{"class":57},[47,4640,4641],{"class":64},"(payload[",[47,4643,773],{"class":155},[47,4645,4646],{"class":64},"])\n",[47,4648,4649],{"class":49,"line":1583},[47,4650,140],{"emptyLinePlaceholder":139},[47,4652,4653,4655,4657],{"class":49,"line":1613},[47,4654,4603],{"class":64},[47,4656,2046],{"class":53},[47,4658,4659],{"class":64}," json.NewEncoder\n",[47,4661,4662],{"class":49,"line":1627},[47,4663,95],{"class":64},[4041,4665,4666],{"v-slot:hints":43},[4044,4667,4668,4673,4679,4685],{},[4047,4669,4049,4670],{},[19,4671,4672],{},"w.Header().Set(\"Content-Type\", \"application\u002Fjson\")",[4047,4674,4675,4676],{},"Потом ",[19,4677,4678],{},"w.WriteHeader(status)",[4047,4680,4681,4682],{},"Затем ",[19,4683,4684],{},"json.NewEncoder(w).Encode(data)",[4047,4686,4687,4688,4691,4692,4694],{},"Последняя строка в ",[19,4689,4690],{},"main"," нужна только чтобы импорт ",[19,4693,4422],{}," не считался лишним до твоей реализации",[16,4696,4697,4698,4701,4702,4704],{},"Следующая статья — ",[3894,4699,4700],{},"Echo",": почему фреймворки удобнее чистого ",[19,4703,21],{}," для типового API, и где всё равно важно понимать стандартную библиотеку.",[4706,4707,4708],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":43,"searchDepth":68,"depth":68,"links":4710},[4711,4714,4717,4722,4728,4729,4730,4731,4732],{"id":32,"depth":68,"text":33,"children":4712},[4713],{"id":329,"depth":92,"text":330},{"id":503,"depth":68,"text":504,"children":4715},[4716],{"id":602,"depth":92,"text":603},{"id":812,"depth":68,"text":813,"children":4718},[4719,4720,4721],{"id":816,"depth":92,"text":817},{"id":1088,"depth":92,"text":1089},{"id":1251,"depth":92,"text":1252},{"id":1719,"depth":68,"text":1720,"children":4723},[4724,4725,4726,4727],{"id":1764,"depth":92,"text":1765},{"id":2065,"depth":92,"text":2066},{"id":2297,"depth":92,"text":2298},{"id":2490,"depth":92,"text":2491},{"id":2720,"depth":68,"text":2721},{"id":3195,"depth":68,"text":3196},{"id":3787,"depth":68,"text":3788},{"id":3889,"depth":68,"text":3890},{"id":4026,"depth":68,"text":4027},1781458312033]