{"id":350225,"date":"2023-07-13T15:01:09","date_gmt":"2023-07-13T15:01:09","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=350225"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=350225","title":{"rendered":"<span>\u0423\u0447\u0438\u043c\u0441\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c REST API \u043d\u0430 Go \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0441\u043e\u043a\u0440\u0430\u0449\u0430\u0442\u0435\u043b\u044f \u0441\u0441\u044b\u043b\u043e\u043a<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/vj\/eb\/gn\/vjebgnmqximd2g6hmph8yit4-ee.png\" data-src=\"https:\/\/habrastorage.org\/webt\/vj\/eb\/gn\/vjebgnmqximd2g6hmph8yit4-ee.png\"\/><\/div>\n<p>  \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 REST API \u0441\u0435\u0440\u0432\u0438\u0441 \u2014 URL Shortener \u2014\u00a0\u0438 \u0437\u0430\u0434\u0435\u043f\u043b\u043e\u0438\u043c \u0435\u0433\u043e \u043d\u0430 <a href=\"https:\/\/selectel.ru\/services\/cloud\/servers\/?utm_source=habr.com&amp;utm_medium=referral&amp;utm_campaign=cloud_article_restapi_130723_content\">\u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440<\/a> \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e GitHub Actions.<\/p>\n<p>  \u0413\u043e\u0432\u043e\u0440\u044f \u00ab\u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439\u00bb, \u044f \u0438\u043c\u0435\u044e \u0432 \u0432\u0438\u0434\u0443, \u0447\u0442\u043e \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043d\u0435 \u0438\u0433\u0440\u0443\u0448\u0435\u0447\u043d\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442, \u0430 \u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044e:<\/p>\n<ul>\n<li>\u043c\u044b \u0432\u044b\u0431\u0435\u0440\u0435\u043c \u0434\u043b\u044f \u043d\u0435\u0433\u043e \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 http-\u0440\u043e\u0443\u0442\u0435\u0440,<\/li>\n<li>\u043f\u043e\u0437\u0430\u0431\u043e\u0442\u0438\u043c\u0441\u044f \u043e \u043b\u043e\u0433\u0430\u0445,<\/li>\n<li>\u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0442\u0435\u0441\u0442\u044b: unit-\u0442\u0435\u0441\u0442\u044b, \u0442\u0435\u0441\u0442\u044b \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u043e\u0432 \u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435,<\/li>\n<li>\u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0434\u0435\u043f\u043b\u043e\u0439 \u0447\u0435\u0440\u0435\u0437 GitHub Actions \u0438 \u0434\u0440.<\/li>\n<\/ul>\n<p>  \u041d\u043e \u0432\u0430\u0436\u043d\u043e \u043f\u043e\u043d\u0438\u043c\u0430\u0442\u044c, \u0447\u0442\u043e \u00ab\u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u043a \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0443\u00bb != \u00ab\u044d\u043d\u0442\u0435\u0440\u043f\u0440\u0430\u0439\u0437\u00bb.<\/p>\n<p>  \u041a\u0440\u0430\u0442\u043a\u043e \u043e\u0431\u043e \u043c\u043d\u0435: \u043c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 <a href=\"https:\/\/t.me\/ntuzov\">\u041d\u0438\u043a\u043e\u043b\u0430\u0439<\/a>, \u044f \u043c\u043d\u043e\u0433\u043e \u043b\u0435\u0442 \u0437\u0430\u043d\u0438\u043c\u0430\u044e\u0441\u044c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u043e\u0439 \u043d\u0430 Go, \u043e\u0447\u0435\u043d\u044c \u043b\u044e\u0431\u043b\u044e \u044d\u0442\u043e\u0442 \u044f\u0437\u044b\u043a. \u0422\u0430\u043a\u0436\u0435 \u0432\u0435\u0434\u0443 \u0441\u0432\u043e\u0439 <a href=\"https:\/\/www.youtube.com\/@nikolay_tuzov\/\">YouTube-\u043a\u0430\u043d\u0430\u043b<\/a>, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0435\u0441\u0442\u044c <a href=\"https:\/\/www.youtube.com\/watch?v=rCJvW2xgnk0\">\u0432\u0438\u0434\u0435\u043e\u0432\u0435\u0440\u0441\u0438\u044f<\/a> \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0433\u0430\u0439\u0434\u0430, \u0441 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u044b\u043c\u0438 \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u044f\u043c\u0438.<br \/>  <a name=\"habracut\"><\/a><\/p>\n<p>  <b>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044e, \u0435\u0441\u043b\u0438 \u043d\u0435\u0442 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0447\u0438\u0442\u0430\u0442\u044c \u0442\u0435\u043a\u0441\u0442 \u0446\u0435\u043b\u0438\u043a\u043e\u043c:<\/b><\/p>\n<p>  \u2192 <a href=\"#1\">\u0412\u044b\u0431\u043e\u0440 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a<\/a><br \/>  \u2192 <a href=\"#2\">\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/a><br \/>  \u2192 <a href=\"#3\">\u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c logger<\/a><br \/>  \u2192 <a href=\"#4\">\u041f\u0438\u0448\u0435\u043c Storage<\/a><br \/>  \u2192 <a href=\"#6\">Handlers \u2014 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432<\/a><br \/>  \u2192 <a href=\"#7\">\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f<\/a><br \/>  \u2192 <a href=\"#8\">\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b<\/a><br \/>  \u2192 <a href=\"#9\">\u0414\u0435\u043f\u043b\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/a><br \/>  \u2192 <a href=\"#12\">\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/a><\/p>\n<p>  <a name=\"1\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>\u0412\u044b\u0431\u043e\u0440 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a<\/h2>\n<p><\/font><br \/>  \u0414\u043b\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/go-chi\/chi\">go-chi\/chi<\/a> \u2014 \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 HTTP-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432,<\/li>\n<li><a href=\"https:\/\/pkg.go.dev\/golang.org\/x\/exp\/slog\">slog<\/a> \u2014 \u0434\u043b\u044f \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f,<\/li>\n<li><a href=\"https:\/\/github.com\/stretchr\/testify\">stretchr\/testify<\/a> \u2014 \u0434\u043b\u044f \u043f\u043e\u043a\u0440\u044b\u0442\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0442\u0435\u0441\u0442\u0430\u043c\u0438,<\/li>\n<li><a href=\"https:\/\/github.com\/ilyakaznacheev\/cleanenv\">ilyakaznacheev\/cleanenv<\/a> \u2014 \u0434\u043b\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f,<\/li>\n<li>SQLite \u2014 \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445, \u0421\u0423\u0411\u0414.<\/li>\n<\/ul>\n<p>  \u0414\u0430\u043b\u0435\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435, \u043f\u043e\u0447\u0435\u043c\u0443 \u044f \u0432\u044b\u0431\u0440\u0430\u043b \u0438\u043c\u0435\u043d\u043d\u043e \u0438\u0445.<\/p>\n<h3>HTTP-\u0440\u043e\u0443\u0442\u0435\u0440<\/h3>\n<p>  \u0420\u0430\u0431\u043e\u0442\u0430 \u0441 HTTP-\u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438 \u2014 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043d\u0430\u0448\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044d\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0432\u0430\u0436\u043d\u044b\u0439 \u0432\u044b\u0431\u043e\u0440.<\/p>\n<p>  \u041c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u0437\u044f\u0442\u044c \u043f\u0430\u043a\u0435\u0442 net\/http \u0438\u0437 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438, \u043d\u043e \u044f \u0440\u0435\u0448\u0438\u043b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442 \u0443\u0434\u043e\u0431\u043d\u0443\u044e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0446\u0438\u044e, \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 middleware \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u043f\u0440\u0438\u044f\u0442\u043d\u044b\u0435 \u0432\u0435\u0449\u0438.<\/p>\n<p>  \u0412 \u0442\u043e \u0436\u0435 \u0432\u0440\u0435\u043c\u044f, \u044f \u0431\u044b \u043d\u0435 \u0445\u043e\u0442\u0435\u043b \u0431\u0440\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e <a href=\"https:\/\/github.com\/gobuffalo\/buffalo\">\u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0441\u043b\u043e\u0436\u043d\u043e\u0435<\/a>. \u0412 \u0438\u0434\u0435\u0430\u043b\u0435 \u043d\u0443\u0436\u043d\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0435 \u0441 net\/http \u0438 \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u043c\u043e\u0435.<\/p>\n<p>  \u042f <a href=\"https:\/\/t.me\/ntuzov\/214\">\u043f\u0440\u043e\u0432\u0435\u043b \u043e\u043f\u0440\u043e\u0441<\/a> \u0432 \u0441\u0432\u043e\u0435\u043c Telegram-\u043a\u0430\u043d\u0430\u043b\u0435, \u0443\u0447\u0435\u043b \u0435\u0433\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0438 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u0447\u0438\u043a\u043e\u0432 \u0438 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0441\u044f \u043d\u0430 go-chi\/chi. \u041e\u043d \u043a\u0430\u043a \u0440\u0430\u0437 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c \u0441 net\/http, \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u0438\u0441\u0442\u0438\u0447\u043d\u044b\u0439 \u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u2014 \u043d\u0430 \u043c\u043e\u0439 \u0432\u0437\u0433\u043b\u044f\u0434, \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 Go-idiomatic.<\/p>\n<h3>\u041b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435<\/h3>\n<p>  \u0417\u0434\u0435\u0441\u044c \u043c\u043e\u0436\u043d\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u0434\u0443\u043c\u0430\u0442\u044c, \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u0437\u044f\u0442\u044c \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0439 uber\/zap \u0438 \u0434\u0432\u0438\u0433\u0430\u0442\u044c\u0441\u044f \u0434\u0430\u043b\u044c\u0448\u0435. \u041d\u043e \u043c\u043d\u0435 \u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043a \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 \u043b\u043e\u0433\u0433\u0435\u0440\u0443. \u041c\u043e\u0436\u043d\u043e, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0442\u043e\u043c \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u043c\u0435\u043d\u044f\u0442\u044c \u043b\u043e\u0433\u0433\u0435\u0440\u044b. \u041e\u0434\u043d\u0430\u043a\u043e \u044d\u0442\u043e \u0441\u043b\u043e\u0436\u043d\u0435\u0435, \u0447\u0435\u043c \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f: \u0441 \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0432\u0435\u0440\u043e\u044f\u0442\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u0437\u0430\u0442\u043e\u0447\u0435\u043d\u043d\u044b\u0439 \u043f\u043e\u0434 \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u043b\u043e\u0433\u0433\u0435\u0440. \u0414\u0430 \u0438 \u0432 \u0446\u0435\u043b\u043e\u043c, \u043f\u043e\u043a\u0430 \u043d\u0435 \u043d\u0430\u0431\u044c\u0435\u0448\u044c \u043a\u0443\u0447\u0443 \u0448\u0438\u0448\u0435\u043a, \u0441\u043b\u043e\u0436\u043d\u043e \u0437\u0430\u0440\u0430\u043d\u0435\u0435 \u043f\u043e\u043d\u044f\u0442\u044c, \u043a\u0430\u043a\u043e\u0439 \u0438\u043c\u0435\u043d\u043d\u043e \u043d\u0430\u0431\u043e\u0440 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u043d\u0430\u043c \u043f\u043e\u0434\u043e\u0439\u0434\u0435\u0442.<\/p>\n<p>  \u041a \u0441\u0447\u0430\u0441\u0442\u044c\u044e, \u0443\u043c\u043d\u044b\u0435 \u043b\u044e\u0434\u0438 \u0443\u0436\u0435 \u043f\u043e\u0434\u0443\u043c\u0430\u043b\u0438 \u0437\u0430 \u043d\u0430\u0441 \u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 <a href=\"https:\/\/github.com\/go-logr\/logr\">go-logr\/logr<\/a>, \u0432 \u0446\u0435\u043b\u043e\u043c, \u043e\u0447\u0435\u043d\u044c \u043c\u043d\u0435 \u043f\u043e\u043d\u0440\u0430\u0432\u0438\u043b\u0441\u044f. \u0421\u043e\u0432\u0435\u0442\u0443\u044e \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435: \u0430\u0432\u0442\u043e\u0440\u044b \u043f\u0440\u043e\u0432\u0435\u043b\u0438 \u0441\u0435\u0440\u044c\u0435\u0437\u043d\u0443\u044e \u0440\u0430\u0431\u043e\u0442\u0443 \u043f\u043e \u043f\u0435\u0440\u0435\u043e\u0441\u043c\u044b\u0441\u043b\u0435\u043d\u0438\u044e \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432 Go.<\/p>\n<p>  \u0414\u0440\u0443\u0433\u043e\u0439 \u0445\u043e\u0440\u043e\u0448\u0438\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u2014 <a href=\"https:\/\/pkg.go.dev\/golang.org\/x\/exp\/slog\">slog<\/a>. \u042d\u0442\u043e \u043f\u0430\u043a\u0435\u0442 \u0434\u043b\u044f \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043e\u0442\u0432\u044f\u0437\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043b\u043e\u0433\u0433\u0435\u0440\u0430 \u0438 \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u043c\u0435\u043d\u044f\u0442\u044c \u0435\u0433\u043e \u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438. \u0411\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430 slog \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u2014 \u044d\u0442\u043e \u0442\u0435\u043c\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438. \u041e\u0441\u0442\u0430\u0432\u043b\u044e \u0442\u043e\u043b\u044c\u043a\u043e <a href=\"https:\/\/t.me\/golang_digest\/71\">\u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u043f\u043e\u0441\u0442<\/a> \u0441 \u0445\u043e\u0440\u043e\u0448\u0435\u0439 \u043f\u043e\u0434\u0431\u043e\u0440\u043a\u043e\u0439 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u043e\u0432 \u043e \u043d\u0435\u043c. \u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u2014 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0438\u043c\u0435\u043d\u043d\u043e slog.<\/p>\n<h3>\u0414\u0440\u0443\u0433\u043e\u0435<\/h3>\n<p>  \u0414\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043c\u043e\u0436\u043d\u043e \u0432\u0437\u044f\u0442\u044c \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0439 <a href=\"https:\/\/github.com\/stretchr\/testify\">testify<\/a> \u0438 <a href=\"https:\/\/github.com\/gavv\/httpexpect\">httpexpect<\/a>, \u0442\u0443\u0442 \u0431\u0435\u0437 \u0441\u044e\u0440\u043f\u0440\u0438\u0437\u043e\u0432.<\/p>\n<p>  \u0414\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043a\u043e\u043d\u0444\u0438\u0433\u0430\u043c\u0438 \u2014 <a href=\"https:\/\/github.com\/ilyakaznacheev\/cleanenv\">cleanenv<\/a>. \u042d\u0442\u043e \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u0438\u0441\u0442\u0438\u0447\u043d\u044b\u0439 \u043f\u0430\u043a\u0435\u0442, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0435\u0441\u0442\u044c \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0435: \u0447\u0442\u0435\u043d\u0438\u0435 \u0438\u0437 \u0432\u0441\u0435\u0445 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u044b\u0445 \u0444\u043e\u0440\u043c\u0430\u0442\u043e\u0432 \u043a\u043e\u043d\u0444\u0438\u0433-\u0444\u0430\u0439\u043b\u043e\u0432, \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0443\u0434\u043e\u0431\u043d\u044b\u0435 struct-\u0442\u0435\u0433\u0438 \u0438 \u0434\u0440\u0443\u0433\u043e\u0435.<\/p>\n<p>  \u0414\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0431\u0435\u0440\u0435\u043c SQLite, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043d\u0435\u0439 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c, \u0438 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043c\u044b \u0438\u043c\u0435\u0435\u043c \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u0443\u044e \u0411\u0414. \u0414\u043b\u044f \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u044d\u0442\u043e \u043e\u0442\u043b\u0438\u0447\u043d\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442.<\/p>\n<p>  <a href=\"https:\/\/selectel.ru\/services\/cloud\/servers\/?utm_source=habr.com&amp;utm_medium=referral&amp;utm_campaign=cloud_article_restapi_130723_banner\"><\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/ic\/fg\/qs\/icfgqs47gczlbjspzmry5ypqazq.png\" data-src=\"https:\/\/habrastorage.org\/webt\/ic\/fg\/qs\/icfgqs47gczlbjspzmry5ypqazq.png\"\/><\/div>\n<p><\/a><\/p>\n<p>  <a name=\"2\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/h2>\n<p><\/font><br \/>  \u041f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043c \u043a \u043a\u043e\u0434\u0443 \u0438 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043c \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0435 \u0434\u043b\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0430\u043f\u043a\u0443 config \u2014 \u0437\u0434\u0435\u0441\u044c \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u0441 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0435\u0439. \u042f \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c yaml, \u043d\u043e \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u043b\u044e\u0431\u043e\u0439 \u0434\u0440\u0443\u0433\u043e\u0439 \u0443\u0434\u043e\u0431\u043d\u044b\u0439 \u0432\u0430\u043c \u0444\u043e\u0440\u043c\u0430\u0442. \u0413\u043b\u0430\u0432\u043d\u043e\u0435, \u0447\u0442\u043e\u0431\u044b \u0435\u0433\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u043b cleanenv.<\/p>\n<p>  \u0418\u0442\u0430\u043a, \u0432 \u043f\u0430\u043f\u043a\u0435 config \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0430\u0439\u043b local.yaml:<\/p>\n<pre><code class=\"plaintext\"># config\/local.yaml  env: \"local\" # \u041e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435 - local, dev \u0438\u043b\u0438 prod storage_path: \".\/storage\/storage.db\" # \u0444\u0430\u0439\u043b, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c\u0441\u044f \u043d\u0430\u0448\u0430 \u0411\u0414 http_server: # \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043d\u0430\u0448\u0435\u0433\u043e http-\u0441\u0435\u0440\u0432\u0435\u0440\u0430   address: \"localhost:8082\"   timeout: 4s   idle_timeout: 30s<\/code><\/pre>\n<p>  <\/p>\n<blockquote><p><i>\u041d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u043e\u0441\u0432\u043e\u0431\u043e\u0434\u0438\u0442\u044c \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u0440\u0442 \u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u0430\u043f\u043a\u0443, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d db-\u0444\u0430\u0439\u043b. \u0421\u0430\u043c \u0444\u0430\u0439\u043b \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e, \u043e\u043d \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438.<\/i><\/p><\/blockquote>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b <code>internal\/config\/config.go<\/code>. \u0417\u0434\u0435\u0441\u044c \u0438 \u0434\u0430\u043b\u0435\u0435 \u044f \u043f\u043e\u0434\u0440\u0430\u0437\u0443\u043c\u0435\u0432\u0430\u044e \u043f\u0443\u0442\u0438 \u043e\u0442 \u043a\u043e\u0440\u043d\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430: \u0435\u0441\u043b\u0438 \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u0443\u0442\u0438 \u0435\u0449\u0435 \u043d\u0435\u0442, \u0435\u0433\u043e \u043d\u0430\u0434\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0442\u0430\u043a:<\/p>\n<pre><code class=\"bash\">mkdir -p internal\/config &amp;&amp; touch internal\/config\/config.go<\/code><\/pre>\n<p>  \u0412 config.go \u0437\u0430\u0432\u0435\u0434\u0435\u043c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0435\u043c \u0430\u043d\u043c\u0430\u0440\u0448\u0430\u043b\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0444\u0430\u0439\u043b:<\/p>\n<pre><code class=\"go\">\/\/ internal\/config\/config.go  type Config struct {     Env         string `yaml:\"env\" env-default:\"development\"`     StoragePath string `yaml:\"storage_path\" env-required:\"true\"`     HTTPServer }  type HTTPServer struct {     Address     string        `yaml:\"address\" env-default:\"0.0.0.0:8080\"`     Timeout     time.Duration `yaml:\"timeout\" env-default:\"5s\"`     IdleTimeout time.Duration `yaml:\"idle_timeout\" env-default:\"60s\"` }<\/code><\/pre>\n<p>  \u0417\u0434\u0435\u0441\u044c \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e c\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 struct-\u0442\u0435\u0433\u0438:<\/p>\n<ul>\n<li>yaml \u2014 \u0438\u043c\u044f \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0433\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u0432 Yaml-\u0444\u0430\u0439\u043b\u0435, <\/li>\n<li>env-default \u2014 \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435,<\/li>\n<li>env-required \u2014 \u0434\u0435\u043b\u0430\u0435\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c\u0438. \u0415\u0441\u043b\u0438 \u0442\u0430\u043a\u043e\u0439 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d, \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0443.<\/li>\n<\/ul>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c cleanenv \u0438 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u043c \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443.<\/p>\n<pre><code class=\"bash\">go get -u github.com\/ilyakaznacheev\/cleanenv<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"go\"> \/\/ internal\/config\/config.go  func MustLoad() *Config {     \/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043f\u0443\u0442\u044c \u0434\u043e \u043a\u043e\u043d\u0444\u0438\u0433-\u0444\u0430\u0439\u043b\u0430 \u0438\u0437 env-\u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 CONFIG_PATH     configPath := os.Getenv(\"CONFIG_PATH\")     if configPath == \"\" {         log.Fatal(\"CONFIG_PATH environment variable is not set\")     }      \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u043e\u043d\u0444\u0438\u0433-\u0444\u0430\u0439\u043b\u0430     if _, err := os.Stat(configPath); err != nil {         log.Fatalf(\"error opening config file: %s\", err)     }      var cfg Config      \/\/ \u0427\u0438\u0442\u0430\u0435\u043c \u043a\u043e\u043d\u0444\u0438\u0433-\u0444\u0430\u0439\u043b \u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043d\u0430\u0448\u0443 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443     err := cleanenv.ReadConfig(configPath, &amp;cfg)     if err != nil {         log.Fatalf(\"error reading config file: %s\", err)     }      return &amp;cfg }<\/code><\/pre>\n<p>  \u041f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430 Must \u0432 \u0438\u043c\u0435\u043d\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043e\u0431\u044b\u0447\u043d\u043e \u0433\u043e\u0432\u043e\u0440\u0438\u0442, \u0447\u0442\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0432\u043c\u0435\u0441\u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430 \u043e\u0448\u0438\u0431\u043a\u0438 \u0430\u0432\u0430\u0440\u0438\u0439\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0431\u0443\u0434\u0435\u0442 \u043f\u0430\u043d\u0438\u043a\u043e\u0432\u0430\u0442\u044c. \u0422\u0430\u043a\u0438\u043c \u043f\u043e\u0434\u0445\u043e\u0434\u043e\u043c \u0437\u043b\u043e\u0443\u043f\u043e\u0442\u0440\u0435\u0431\u043b\u044f\u0442\u044c \u043d\u0435 \u0441\u0442\u043e\u0438\u0442, \u043d\u043e \u0438\u043d\u043e\u0433\u0434\u0430 \u044d\u0442\u043e \u0431\u044b\u0432\u0430\u0435\u0442 \u0443\u0434\u043e\u0431\u043d\u043e. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0435\u0441\u043b\u0438 \u0432\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0443\u043f\u0430\u0434\u0435\u0442 \u0441 \u043f\u0430\u043d\u0438\u043a\u043e\u0439 \u0438\u0437-\u0437\u0430 \u043a\u0440\u0438\u0432\u043e\u0433\u043e \u0438\u043b\u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0433\u043e \u043a\u043e\u043d\u0444\u0438\u0433-\u0444\u0430\u0439\u043b\u0430, \u044d\u0442\u043e \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e. \u0410 \u0432\u043e\u0442 \u0432 \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0435 \u0442\u0430\u043a\u043e\u0433\u043e \u043b\u0443\u0447\u0448\u0435 \u043d\u0435 \u0434\u043e\u043f\u0443\u0441\u043a\u0430\u0442\u044c\u2026<\/p>\n<p>  \u0422\u0430\u043a\u0436\u0435 \u043e\u0431\u0440\u0430\u0449\u0430\u044e \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u043f\u0443\u0442\u044c \u0434\u043e \u043a\u043e\u043d\u0444\u0438\u0433-\u0444\u0430\u0439\u043b\u0430 \u044f \u043f\u043e\u043b\u0443\u0447\u0430\u044e \u0438\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f CONFIG_PATH, \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u043d\u0435 \u043f\u0440\u0435\u0434\u0443\u0441\u043c\u043e\u0442\u0440\u0435\u043d. \u0427\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0442\u0430\u043a\u043e\u0439 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439, \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439:<\/p>\n<pre><code class=\"bash\">CONFIG_PATH=.\/config\/local.yaml .\/your-app<\/code><\/pre>\n<p>  <\/p>\n<blockquote><p><i>\u0415\u0441\u0442\u044c \u0438 \u0431\u043e\u043b\u0435\u0435 \u0443\u0434\u043e\u0431\u043d\u044b\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u044b, \u043d\u043e \u043e\u043d\u0438 \u0437\u0430\u0432\u0438\u0441\u044f\u0442 \u043e\u0442 \u0432\u0430\u0448\u0435\u0433\u043e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0439 IDE \u0438 \u0442.\u043f. \u0421\u043e\u0432\u0435\u0442\u0443\u044e \u0438\u0437\u0443\u0447\u0438\u0442\u044c \u044d\u0442\u043e\u0442 \u0432\u043e\u043f\u0440\u043e\u0441 \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e.<\/i><\/p><\/blockquote>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043f\u0430\u043f\u043a\u0443 cmd \u2014 \u0437\u0434\u0435\u0441\u044c \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432\u0441\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0437\u0430\u043f\u0443\u0441\u043a \u0441\u0430\u043c\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430). \u0412 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u0437\u0434\u0435\u0441\u044c \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0443\u0442\u0438\u043b\u0438\u0442\u044b, \u043c\u043e\u043a\u0438 \u0438 \u0434\u0440\u0443\u0433\u043e\u0435.<\/p>\n<p>  \u0414\u0430\u043b\u0435\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0432 cmd \u043f\u0430\u043f\u043a\u0443 url-shortener, \u0430 \u0432\u043d\u0443\u0442\u0440\u0438 \u043d\u0435\u0435 \u2014 \u0444\u0430\u0439\u043b main.go. \u0417\u0434\u0435\u0441\u044c \u0431\u0443\u0434\u0435\u043c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u043d\u0430\u0448 \u0441\u0435\u0440\u0432\u0438\u0441 \u2014 \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0438 MustLoad():<\/p>\n<pre><code class=\"go\">\/\/ cmd\/url-shortener\/main.go  package main  import (     \"url-shortener\/internal\/config\" )  func main() {     cfg := config.MustLoad() }<\/code><\/pre>\n<p>  <a name=\"3\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>\u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c logger<\/h2>\n<p><\/font><br \/>  \u041e\u0431\u044a\u0435\u043a\u0442 \u043a\u043e\u043d\u0444\u0438\u0433\u0430 \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c, \u0442\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0431\u0435\u0440\u0435\u043c \u043b\u043e\u0433\u0433\u0435\u0440. \u041a\u0430\u043a \u044f \u043f\u0438\u0441\u0430\u043b \u0432\u044b\u0448\u0435, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u0443\u0434\u0435\u043c <a href=\"https:\/\/pkg.go.dev\/golang.org\/x\/exp\/slog\">slog<\/a>. \u042d\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0433\u0438\u0431\u043a\u0438\u0439 \u043f\u0430\u043a\u0435\u0442, \u0438 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0440\u0430\u0437\u043d\u043e\u0439. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0445\u0435\u043d\u0434\u043b\u0435\u0440 (\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043b\u043e\u0433\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442, \u0447\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441 \u0437\u0430\u043f\u0438\u0441\u044f\u043c\u0438), \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u044c \u0432 \u043d\u0435\u0433\u043e \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0439 \u043b\u043e\u0433\u0433\u0435\u0440 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, zap \u0438\u043b\u0438 logrus) \u043b\u0438\u0431\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043f\u0430\u043a\u0435\u0442\u043e\u043c. \u042f \u0432\u044b\u0431\u0435\u0440\u0443 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442.<\/p>\n<p>  \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c slog:<\/p>\n<pre><code class=\"bash\">go get golang.org\/x\/exp\/slog<\/code><\/pre>\n<p>  \u0415\u0441\u043b\u0438 \u0432\u044b \u0447\u0438\u0442\u0430\u0435\u0442\u0435 \u0441\u0442\u0430\u0442\u044c\u044e \u0443\u0436\u0435 \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u0445\u043e\u0434\u0430 Go 1.21, \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c slog \u0438\u0437 std lib:<\/p>\n<pre><code class=\"go\">import \"log\/slog\"<\/code><\/pre>\n<p>  \u0418\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438 \u0432 slog \u0435\u0441\u0442\u044c \u0434\u0432\u0430 \u0432\u0438\u0434\u0430 \u0445\u0435\u043d\u0434\u043b\u0435\u0440\u043e\u0432. \u0414\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043d\u0430\u043c \u043f\u043e\u0434\u043e\u0439\u0434\u0435\u0442 TextHandler, \u0430 \u0434\u043b\u044f \u0434\u0435\u043f\u043b\u043e\u044f \u043b\u0443\u0447\u0448\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c JSONHandler, \u0447\u0442\u043e\u0431\u044b \u0430\u0433\u0440\u0435\u0433\u0430\u0442\u043e\u0440 \u043b\u043e\u0433\u043e\u0432 (Kibana, Grafana, Loki \u0438 \u0434\u0440\u0443\u0433\u0438\u0435) \u043c\u043e\u0433 \u0435\u0433\u043e \u0440\u0430\u0441\u043f\u0430\u0440\u0441\u0438\u0442\u044c.<\/p>\n<p>  \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0432\u0430\u0436\u043d\u043e \u0443\u0447\u0435\u0441\u0442\u044c \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u2014 \u044d\u0442\u043e \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c\u0441\u044f. \u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u0435\u0441\u043b\u0438 \u043c\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u0443\u0440\u043e\u0432\u0435\u043d\u044c Info, \u0442\u043e Debug-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043d\u0435 \u0443\u0432\u0438\u0434\u0438\u043c. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0438 Dev-\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u043b\u0443\u0447\u0448\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0443\u0440\u043e\u0432\u0435\u043d\u044c Debug, \u0430 \u0434\u043b\u044f \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0430 \u2014 Info.<\/p>\n<p>  \u0414\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u0432\u044b\u043d\u0435\u0441\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043b\u043e\u0433\u0433\u0435\u0440\u0430 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e:<\/p>\n<pre><code class=\"go\">\/\/ cmd\/url-shortener\/main.go const (     envLocal = \"local\"     envDev   = \"dev\"     envProd  = \"prod\" )  func main() {     \/\/ ... }  func setupLogger(env string) *slog.Logger {     var log *slog.Logger      switch env {     case envLocal:         log = slog.New(slog.NewTextHandler(os.Stdout, &amp;slog.HandlerOptions{Level: slog.LevelDebug}))     case envDev, envProd:         log = slog.New(slog.NewJSONHandler(os.Stdout, &amp;slog.HandlerOptions{Level: slog.LevelDebug}))     case envProd:         log = slog.New(slog.NewJSONHandler(os.Stdout, &amp;slog.HandlerOptions{Level: slog.LevelInfo}))     }      return log }<\/code><\/pre>\n<p>  <\/p>\n<blockquote><p><i>\u0412 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u044d\u0442\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043b\u043e\u0433\u0433\u0435\u0440 \u0441 \u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u2014 <\/i><i>TextHandler<\/i><i> \/ <\/i><i>JSONHandler<\/i><i> \u0438 \u0443\u0440\u043e\u0432\u0435\u043d\u044c LevelDebug \/ LevelInfo.<\/i><\/p><\/blockquote>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043b\u043e\u0433\u0433\u0435\u0440 \u0432 main, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 env \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u0430 log.With \u0438 \u0432\u044b\u0432\u0435\u0434\u0435\u043c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f:<\/p>\n<pre><code class=\"go\">\/\/ cmd\/url-shortener\/main.go  func main() {     cfg := config.MustLoad()      log := setupLogger(cfg.Env)     log = log.With(slog.String(\"env\", cfg.Env)) \/\/ \u043a \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044e \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043f\u043e\u043b\u0435 \u0441 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439 \u043e \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0438      log.Info(\"initializing server\", slog.String(\"address\", cfg.Address)) \/\/ \u041f\u043e\u043c\u0438\u043c\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432\u044b\u0432\u0435\u0434\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u0441 \u0430\u0434\u0440\u0435\u0441\u043e\u043c     log.Debug(\"logger debug mode enabled\") }<\/code><\/pre>\n<p>  \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0432\u044b\u0432\u043e\u0434:<\/p>\n<pre><code class=\"bash\">time=2023-06-18T19:27:41.720+06:00 level=INFO msg=\"initializing server\" env=local address=localhost:8082 time=2023-06-18T19:27:41.720+06:00 level=DEBUG msg=\"logger debug mode enabled\" env=local<\/code><\/pre>\n<p>  <\/p>\n<blockquote><p><i>\u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <\/i><i>With<\/i><i> \u043a \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044e \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u043f\u043e\u043b\u0435 env \u0441 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439 \u043e \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0438. \u042d\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0443\u0434\u043e\u0431\u043d\u043e, \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u043f\u043e\u043b\u0443\u0447\u0448\u0435 \u0438\u0437\u0443\u0447\u0438\u0442\u044c \u044d\u0442\u0443 \u043c\u0435\u0445\u0430\u043d\u0438\u043a\u0443 \u0438 \u043e\u0431\u043e\u0433\u0430\u0449\u0430\u0442\u044c \u0441\u0432\u043e\u0439 \u043b\u043e\u0433\u0433\u0435\u0440 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439.<\/i><\/p><\/blockquote>\n<p>  \u041f\u043e\u043c\u0438\u043c\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0445 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0439, \u043d\u0430\u043c \u0432\u0441\u0435 \u0436\u0435 \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043e\u0434\u043d\u0443 \u0441\u0432\u043e\u044e \u2014 DiscardHandler. \u0412 \u0442\u0430\u043a\u043e\u043c \u0432\u0438\u0434\u0435 \u043b\u043e\u0433\u0433\u0435\u0440 \u0431\u0443\u0434\u0435\u0442 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u0432 \u043d\u0435\u0433\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c, \u2014 \u044d\u0442\u043e \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0432 \u0442\u0435\u0441\u0442\u0430\u0445. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043f\u0430\u043a\u0435\u0442 slogdiscard \u0438 \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u0435\u043c \u0432 \u043d\u0435\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 slog.Handler:<\/p>\n<pre><code class=\"go\">\/\/ internal\/lib\/logger\/handlers\/slogdiscard\/slogdiscard.go package slogdiscard  import (     \"context\"      \"golang.org\/x\/exp\/slog\" )  func NewDiscardLogger() *slog.Logger {     return slog.New(NewDiscardHandler()) }  type DiscardHandler struct{}  func NewDiscardHandler() *DiscardHandler {     return &amp;DiscardHandler{} }  func (h *DiscardHandler) Handle(_ context.Context, _ slog.Record) error {     \/\/ \u041f\u0440\u043e\u0441\u0442\u043e \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0435\u043c \u0437\u0430\u043f\u0438\u0441\u044c \u0436\u0443\u0440\u043d\u0430\u043b\u0430     return nil }  func (h *DiscardHandler) WithAttrs(_ []slog.Attr) slog.Handler {     \/\/ \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u043e\u0442 \u0436\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0435\u0442 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432 \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f     return h }  func (h *DiscardHandler) WithGroup(_ string) slog.Handler {     \/\/ \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u043e\u0442 \u0436\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0435\u0442 \u0433\u0440\u0443\u043f\u043f\u044b \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f     return h }  func (h *DiscardHandler) Enabled(_ context.Context, _ slog.Level) bool {     \/\/ \u0412\u0441\u0435\u0433\u0434\u0430 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 false, \u0442\u0430\u043a \u043a\u0430\u043a \u0437\u0430\u043f\u0438\u0441\u044c \u0436\u0443\u0440\u043d\u0430\u043b\u0430 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f     return false }<\/code><\/pre>\n<p>  \u0422\u0430\u043a\u0436\u0435 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u0430\u043a\u0435\u0442 sl (\u0441\u043e\u043a\u0440\u0430\u0449\u0435\u043d\u043d\u043e \u043e\u0442 slog), \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043b\u043e\u0433\u0433\u0435\u0440\u043e\u043c. \u041e\u043d\u0438 \u043f\u0440\u0438\u0433\u043e\u0434\u044f\u0442\u0441\u044f \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c.<\/p>\n<pre><code class=\"go\">\/\/ internal\/lib\/logger\/sl\/sl.go package sl  import (     \"golang.org\/x\/exp\/slog\"      \"url-shortener\/internal\/lib\/logger\/handlers\/slogdiscard\" )  func Err(err error) slog.Attr {     return slog.Attr{         Key:   \"error\",         Value: slog.StringValue(err.Error()),     } }<\/code><\/pre>\n<p>  <a name=\"4\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>\u041f\u0438\u0448\u0435\u043c Storage<\/h2>\n<p><\/font><br \/>  \u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u0443\u0447\u0438\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e, \u0441 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043e\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c. \u0425\u0440\u0430\u043d\u0438\u0442\u044c \u0431\u0443\u0434\u0435\u043c \u0432\u0441\u0435\u0433\u043e \u043e\u0434\u043d\u0443 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c \u2014 \u0441\u0441\u044b\u043b\u043a\u0443 \u0441 \u0434\u0432\u0443\u043c\u044f \u043f\u043e\u043b\u044f\u043c\u0438:<\/p>\n<ul>\n<li>url \u2014 \u0434\u043b\u0438\u043d\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c, <\/li>\n<li>alias \u2014 \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043a\u0430\u0442\u044c \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441.<\/li>\n<\/ul>\n<p>  \u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 \u0442\u0430\u0431\u043b\u0438\u0446\u044b:<\/p>\n<pre><code class=\"sql\">CREATE TABLE IF NOT EXISTS url(         id INTEGER PRIMARY KEY,         alias TEXT NOT NULL UNIQUE,         url TEXT NOT NULL); CREATE INDEX IF NOT EXISTS idx_alias ON url(alias);<\/code><\/pre>\n<p>  \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435:<\/p>\n<ul>\n<li>\u043f\u043e\u043b\u0435 alias \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0435 (\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 UNIQUE), \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0431\u044b\u043b\u043e \u043a\u043e\u043b\u043b\u0438\u0437\u0438\u0439, <\/li>\n<li>\u0432\u0441\u0435 \u043f\u043e\u043b\u044f \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435, <\/li>\n<li>\u0434\u043b\u044f \u0431\u044b\u0441\u0442\u0440\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430 \u0437\u0430\u043f\u0438\u0441\u0435\u0439 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0438\u043d\u0434\u0435\u043a\u0441 idx_alias.<\/li>\n<\/ul>\n<p>  \u0417\u0434\u0435\u0441\u044c \u043c\u044b \u043c\u043e\u0433\u043b\u0438 \u0431\u044b \u043e\u0431\u043e\u0439\u0442\u0438\u0441\u044c \u0434\u0430\u0436\u0435 \u0431\u0435\u0437 \u043f\u043e\u043b\u0435 id \u0438 \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u0442\u043e\u043b\u044c\u043a\u043e alias, \u043d\u043e \u043c\u043d\u0435 \u0442\u0430\u043a\u043e\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0437\u0430\u043f\u0438\u0441\u0438 \u0431\u0443\u0434\u0443\u0442 \u0443\u0434\u0430\u043b\u044f\u0442\u044c\u0441\u044f \u0438 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c\u0441\u044f \u0441 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0449\u0438\u043c\u0438\u0441\u044f \u0430\u043b\u0438\u0430\u0441\u0430\u043c\u0438, \u043d\u043e id \u0432\u0441\u0435\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u043c. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u043a\u043e\u0433\u0434\u0430-\u043d\u0438\u0431\u0443\u0434\u044c \u043f\u043e\u043c\u043e\u0447\u044c \u0432 \u0434\u0435\u0431\u0430\u0433\u0435 \u0438 \u0442.\u043f.<\/p>\n<p>  \u041a\u043e\u0434 Storage \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u043f\u0430\u043f\u043a\u0435 internal\/storage \u2014 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432 \u043d\u0435\u0439 \u0444\u0430\u0439\u043b storage.go. \u0412 \u043d\u0435\u043c \u0431\u0443\u0434\u0435\u0442 \u043b\u0438\u0448\u044c \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0439 \u043a\u043e\u0434. \u0421\u0435\u0439\u0447\u0430\u0441 \u0442\u0430\u043a\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u043c\u0430\u043b\u043e \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u043e\u0448\u0438\u0431\u043a\u0430\u0445:<\/p>\n<pre><code class=\"go\">\/\/ internal\/storage\/storage.go  package storage  import \"errors\"  var (     ErrURLNotFound = errors.New(\"url not found\")     ErrURLExists   = errors.New(\"url exists\") )<\/code><\/pre>\n<p>  \u0414\u0430\u043b\u0435\u0435 \u0437\u0434\u0435\u0441\u044c \u0436\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u0430\u043f\u043a\u0443 sqlite, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0431\u0443\u0434\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u043a\u043e\u0434 \u0434\u043b\u044f \u044d\u0442\u043e\u0439 \u0421\u0423\u0411\u0414. \u0415\u0441\u043b\u0438 \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u0437\u0430\u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u0435\u0440\u0435\u0435\u0445\u0430\u0442\u044c \u043d\u0430 \u0434\u0440\u0443\u0433\u043e\u0439 \u0442\u0438\u043f \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430, \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0440\u044f\u0434\u043e\u043c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u043f\u0430\u043f\u043a\u0443 \u0438 \u043d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u0443\u044e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043c\u044b \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043a \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 sqlite \u0438 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0434\u043b\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u0430 Storage.<\/p>\n<pre><code class=\"bash\">go get github.com\/mattn\/go-sqlite3<\/code><\/pre>\n<pre><code class=\"go\"> \/\/ internal\/storage\/sqlite\/sqlite.go  type Storage struct {     db *sql.DB }<\/code><\/pre>\n<p>  \u0418 \u0435\u0433\u043e \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440:<\/p>\n<pre><code class=\"go\">\/\/ internal\/storage\/sqlite\/sqlite.go  func New(storagePath string) (*Storage, error) {     const op = \"storage.sqlite.NewStorage\" \/\/ \u0418\u043c\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0434\u043b\u044f \u043b\u043e\u0433\u043e\u0432 \u0438 \u043e\u0448\u0438\u0431\u043e\u043a      db, err := sql.Open(\"sqlite3\", storagePath) \/\/ \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043a \u0411\u0414     if err != nil {         return nil, fmt.Errorf(\"%s: %w\", op, err)     }      \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0442\u0430\u0431\u043b\u0438\u0446\u0443, \u0435\u0441\u043b\u0438 \u0435\u0435 \u0435\u0449\u0435 \u043d\u0435\u0442     stmt, err := db.Prepare(`     CREATE TABLE IF NOT EXISTS url(         id INTEGER PRIMARY KEY,         alias TEXT NOT NULL UNIQUE,         url TEXT NOT NULL);     CREATE INDEX IF NOT EXISTS idx_alias ON url(alias);     `)     if err != nil {         return nil, fmt.Errorf(\"%s: %w\", op, err)     }      _, err = stmt.Exec()     if err != nil {         return nil, fmt.Errorf(\"%s: %w\", op, err)     }      return &amp;Storage{db: db}, nil }<\/code><\/pre>\n<p>  \u0417\u0430\u0447\u0435\u043c \u0437\u0434\u0435\u0441\u044c \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u0430 op? \u042f \u0441\u0442\u0430\u0440\u0430\u044e\u0441\u044c \u0432\u0441\u0435\u0433\u0434\u0430 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0438\u043c\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0432 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u044b\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 \u0438 \u0432 \u043b\u043e\u0433\u0433\u0435\u0440, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0442\u043e\u043c \u0431\u044b\u043b\u043e \u043f\u0440\u043e\u0449\u0435 \u00ab\u0438\u0441\u043a\u0430\u0442\u044c \u0445\u0432\u043e\u0441\u0442\u044b\u00bb \u0432 \u043b\u043e\u0433\u0430\u0445. \u0412\u0435\u0434\u044c \u0440\u0430\u0437\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0447\u0430\u0441\u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044e\u0442 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 \u0438 \u043f\u0438\u0448\u0443\u0442 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0435 \u043b\u043e\u0433\u0438, \u0430 \u043d\u0430\u043c \u043e\u0431\u044b\u0447\u043d\u043e \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u043d\u0438\u043c\u0430\u0442\u044c, \u0433\u0434\u0435 \u0438\u043c\u0435\u043d\u043d\u043e \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u0435.<\/p>\n<p>  \u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u044f \u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u044e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0435\u0439 sql.Open \u043e\u0448\u0438\u0431\u043a\u0443 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c: fmt.Errorf(&#171;%s: %w&#187;, op, err).<\/p>\n<blockquote><p>\u041f\u043e \u043f\u043e\u0432\u043e\u0434\u0443 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u043f\u0440\u044f\u043c\u043e \u0432 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435. \u0414\u043b\u044f \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u044d\u0442\u043e\u0433\u043e \u0432\u043f\u043e\u043b\u043d\u0435 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e. \u041d\u043e, \u0435\u0441\u043b\u0438 \u0432\u044b \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u0442\u0435 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0441\u0435\u0440\u044c\u0435\u0437\u043d\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442, \u0432\u0430\u0436\u043d\u043e \u0437\u0430\u0434\u0443\u043c\u0430\u0442\u044c\u0441\u044f \u043e \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u043c \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439. \u0412\u043f\u0440\u043e\u0447\u0435\u043c, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0440\u0438\u043a\u0440\u0443\u0442\u0438\u0442\u044c \u0435\u0433\u043e \u0447\u0443\u0442\u044c \u043f\u043e\u0437\u0436\u0435, \u0435\u0441\u043b\u0438 \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c.<\/p><\/blockquote>\n<p>  <\/p>\n<h3>\u041c\u0435\u0442\u043e\u0434\u044b \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430<\/h3>\n<p>  \u0423 \u043d\u0430\u0448\u0435\u0433\u043e \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u0441\u0435\u0433\u043e \u0434\u0432\u0430 \u043c\u0435\u0442\u043e\u0434\u0430 \u2014 SaveURL() \u0438 GetURL(). \u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u043f\u0435\u0440\u0432\u043e\u0433\u043e:<\/p>\n<pre><code class=\"go\">\/\/ internal\/storage\/sqlite\/sqlite.go  func (s *Storage) SaveURL(urlToSave string, alias string) (int64, error) {     const op = \"storage.sqlite.SaveURL\"      \/\/ \u041f\u043e\u0434\u0433\u043e\u0442\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441     stmt, err := s.db.Prepare(\"INSERT INTO url(url,alias) values(?,?)\")     if err != nil {         return 0, fmt.Errorf(\"%s: prepare statement: %w\", op, err)     }      \/\/ \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441     res, err := stmt.Exec(urlToSave, alias)     if err != nil {         if sqliteErr, ok := err.(sqlite3.Error); ok &amp;&amp; sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique {             return 0, fmt.Errorf(\"%s: %w\", op, storage.ErrURLExists)         }          return 0, fmt.Errorf(\"%s: execute statement: %w\", op, err)     }      \/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c ID \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438     id, err := res.LastInsertId()     if err != nil {         return 0, fmt.Errorf(\"%s: failed to get last insert id: %w\", op, err)     }      \/\/ \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c ID     return id, nil }<\/code><\/pre>\n<p>  \u0422\u0443\u0442 \u0432\u0441\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0438 \u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u043f\u043e\u044f\u0441\u043d\u044e \u0442\u043e\u043b\u044c\u043a\u043e \u044d\u0442\u0443 \u0441\u0442\u0440\u043e\u0447\u043a\u0443:<\/p>\n<pre><code class=\"go\">if sqliteErr, ok := err.(sqlite3.Error); ok &amp;&amp; sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique { \/\/ \u2026<\/code><\/pre>\n<p>  \u0417\u0434\u0435\u0441\u044c \u043c\u044b \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u043c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u0443\u044e \u043e\u0448\u0438\u0431\u043a\u0443 \u043a\u043e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u043c\u0443 \u0442\u0438\u043f\u0443 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 sqlite3, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c, \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043b\u0438 \u044d\u0442\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 sqlite3.ErrConstraintUnique. \u0415\u0441\u043b\u0438 \u044d\u0442\u043e \u0442\u0430\u043a, \u0437\u043d\u0430\u0447\u0438\u0442, \u043c\u044b \u043f\u043e\u043f\u044b\u0442\u0430\u043b\u0438\u0441\u044c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0434\u0443\u0431\u043b\u0438\u043a\u0430\u0442 \u0438\u043c\u0435\u044e\u0449\u0435\u0439\u0441\u044f \u0437\u0430\u043f\u0438\u0441\u0438. \u041e\u0431 \u044d\u0442\u043e\u043c \u043c\u044b \u0441\u043e\u043e\u0431\u0449\u0438\u043c \u0432 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u0432\u0435\u0440\u043d\u0443\u0432 \u0443\u0436\u0435 \u0441\u0432\u043e\u044e \u043e\u0448\u0438\u0431\u043a\u0443 \u0434\u043b\u044f \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u0438: storage.ErrURLExists. \u041f\u043e\u043b\u0443\u0447\u0438\u0432 \u0435\u0435, \u0441\u0435\u0440\u0432\u0435\u0440 \u0441\u043c\u043e\u0436\u0435\u0442 \u0441\u043e\u043e\u0431\u0449\u0438\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u0442\u0430\u043a\u043e\u0439 alias \u0443 \u043d\u0430\u0441 \u0443\u0436\u0435 \u0435\u0441\u0442\u044c.<\/p>\n<p>  \u0422\u0443\u0442 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u0442\u0443\u043f\u0438\u0442\u044c \u0438\u043d\u0430\u0447\u0435: \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e SELECT \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0437\u0430\u043f\u0438\u0441\u0438 \u043f\u0440\u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 \u0434\u0443\u0431\u043b\u0438\u043a\u0430\u0442\u043e\u0432. \u041d\u043e \u0442\u043e\u0433\u0434\u0430 \u0431\u044b \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u043b\u0438\u0441\u044c \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438, \u0438 \u043a\u043e\u0434 \u0441\u0442\u0430\u043b \u0431\u044b \u0441\u043b\u043e\u0436\u043d\u0435\u0435.<\/p>\n<p>  \u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043f\u0438\u0448\u0435\u043c \u043c\u0435\u0442\u043e\u0434 GetURL():<\/p>\n<pre><code class=\"go\">\/\/ internal\/storage\/sqlite\/sqlite.go  func (s *Storage) GetURL(alias string) (string, error) {     const op = \"storage.sqlite.GetURL\"      stmt, err := s.db.Prepare(\"SELECT url FROM url WHERE alias = ?\")     if err != nil {         return \"\", fmt.Errorf(\"%s: prepare statement: %w\", op, err)     }      var resURL string          err = stmt.QueryRow(alias).Scan(&amp;resURL)     if errors.Is(err, sql.ErrNoRows) {         return \"\", storage.ErrURLNotFound     }     if err != nil {         return \"\", fmt.Errorf(\"%s: execute statement: %w\", op, err)     }      return resURL, nil }<\/code><\/pre>\n<p>  <\/p>\n<blockquote><p><i>\u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0437\u0434\u0435\u0441\u044c \u043f\u043e\u044f\u0441\u043d\u0435\u043d\u0438\u044f \u043d\u0435 \u043d\u0443\u0436\u043d\u044b. \u041c\u0435\u0442\u043e\u0434 DeleteURL \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e, \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0443\u043f\u0440\u0430\u0436\u043d\u0435\u043d\u0438\u044f.<\/i><\/p><\/blockquote>\n<p>  \u041d\u0430\u043a\u043e\u043d\u0435\u0446 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 Storage \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u044e main:<\/p>\n<pre><code class=\"go\">\/\/ cmd\/url-shortener\/main.go  func main() {     \/\/ ...     storage, err := sqlite.New(cfg.StoragePath)     if err != nil {         log.Error(\"failed to initialize storage\", sl.Err(err))     }<\/code><\/pre>\n<p>  \u0417\u0430\u0431\u0435\u0433\u0430\u044f \u0432\u043f\u0435\u0440\u0435\u0434, \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043b\u044f Storage \u043c\u044b \u0442\u0443\u0442 \u043e\u0431\u044a\u044f\u0432\u043b\u044f\u0442\u044c \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u2014 \u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u043c\u0435\u0441\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f. \u041c\u043e\u0442\u0438\u0432\u0430\u0446\u0438\u0435\u0439 \u0442\u0430\u043a\u043e\u0433\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u044f \u0434\u0435\u043b\u0438\u043b\u0441\u044f \u0432 <a href=\"https:\/\/youtu.be\/eYHCCht8eX4\">\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u043c \u0440\u043e\u043b\u0438\u043a\u0435<\/a>.<\/p>\n<h2>\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 HTTP Server<\/h2>\n<p>  \u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0441\u0430\u043c\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u043c\u0443 \u2014 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 HTTP-\u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c. \u041f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u043d\u0430\u0448 chi:<\/p>\n<pre><code class=\"bash\">go get -u github.com\/go-chi\/chi\/v5<\/code><\/pre>\n<p>  \u0418 \u0435\u0449\u0435 \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u043f\u0430\u043a\u0435\u0442 go-chi\/render, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438\u0434\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u043e\u0442 \u0440\u043e\u0443\u0442\u0435\u0440\u0430:<\/p>\n<pre><code class=\"bash\">go get github.com\/go-chi\/render<\/code><\/pre>\n<h3>Middleware<\/h3>\n<p>  \u0412 main \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u0440\u043e\u0443\u0442\u0435\u0440\u0430 \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u043a \u043d\u0435\u043c\u0443 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0439 middleware:<\/p>\n<pre><code class=\"go\">\/\/ cmd\/url-shortener\/main.go  router := chi.NewRouter()      router.Use(middleware.RequestID) \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 request_id \u0432 \u043a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441, \u0434\u043b\u044f \u0442\u0440\u0435\u0439\u0441\u0438\u043d\u0433\u0430 router.Use(middleware.Logger) \/\/ \u041b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 router.Use(middleware.Recoverer)  \/\/ \u0415\u0441\u043b\u0438 \u0433\u0434\u0435-\u0442\u043e \u0432\u043d\u0443\u0442\u0440\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 (\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430) \u043f\u0440\u043e\u0438\u0437\u043e\u0439\u0434\u0435\u0442 \u043f\u0430\u043d\u0438\u043a\u0430, \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0443\u043f\u0430\u0441\u0442\u044c router.Use(middleware.URLFormat) \/\/ \u041f\u0430\u0440\u0441\u0435\u0440 URL\u043e\u0432 \u043f\u043e\u0441\u0442\u0443\u043f\u0430\u044e\u0449\u0438\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432<\/code><\/pre>\n<p>  \u0412\u0441\u0435 \u044d\u0442\u0438 middleware \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438 \u0432 \u043f\u0430\u043a\u0435\u0442\u0435 chi. \u041e\u0431\u0441\u0443\u0434\u0438\u043c \u0442\u0443\u0442 \u043f\u0430\u0440\u0443 \u043c\u043e\u043c\u0435\u043d\u0442\u043e\u0432.<\/p>\n<p>  \u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e middleware.Logger \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u0432\u043e\u0439 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u043b\u043e\u0433\u0433\u0435\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0436\u0435\u043b\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0441\u044f \u043d\u0430\u0448. \u0418\u043d\u0430\u0447\u0435 \u043c\u043e\u0433\u0443\u0442 \u0432\u043e\u0437\u043d\u0438\u043a\u043d\u0443\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0441\u043e \u0441\u0431\u043e\u0440\u043e\u043c \u043b\u043e\u0433\u043e\u0432. \u041b\u0438\u0431\u043e \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 middleware \u0434\u043b\u044f \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432:<\/p>\n<pre><code class=\"go\">\/\/ internal\/http-server\/middleware\/logger\/logger.go  package logger  import (     \"net\/http\"     \"time\"      \"github.com\/go-chi\/chi\/v5\/middleware\"     \"golang.org\/x\/exp\/slog\" )  func New(log *slog.Logger) func(next http.Handler) http.Handler {     return func(next http.Handler) http.Handler {         log = log.With(             slog.String(\"component\", \"middleware\/logger\"),         )          log.Info(\"logger middleware enabled\")          \/\/ \u043a\u043e\u0434 \u0441\u0430\u043c\u043e\u0433\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430         fn := func(w http.ResponseWriter, r *http.Request) {             \/\/ \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u0438\u0441\u0445\u043e\u0434\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0435             entry := log.With(                 slog.String(\"method\", r.Method),                 slog.String(\"path\", r.URL.Path),                 slog.String(\"remote_addr\", r.RemoteAddr),                 slog.String(\"user_agent\", r.UserAgent()),                 slog.String(\"request_id\", middleware.GetReqID(r.Context())),             )                          \/\/ \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u0435\u0440\u0442\u043a\u0443 \u0432\u043e\u043a\u0440\u0443\u0433 `http.ResponseWriter`             \/\/ \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u0432\u0435\u0434\u0435\u043d\u0438\u0439 \u043e\u0431 \u043e\u0442\u0432\u0435\u0442\u0435             ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)              \/\/ \u041c\u043e\u043c\u0435\u043d\u0442 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u0432\u0440\u0435\u043c\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438             t1 := time.Now()                          \/\/ \u0417\u0430\u043f\u0438\u0441\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u0432 \u043b\u043e\u0433 \u0432 defer             \/\/ \u0432 \u044d\u0442\u043e\u0442 \u043c\u043e\u043c\u0435\u043d\u0442 \u0437\u0430\u043f\u0440\u043e\u0441 \u0443\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d             defer func() {                 entry.Info(\"request completed\",                     slog.Int(\"status\", ww.Status()),                     slog.Int(\"bytes\", ww.BytesWritten()),                     slog.String(\"duration\", time.Since(t1).String()),                 )             }()              \/\/ \u041f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0443 \u0432 \u0446\u0435\u043f\u043e\u0447\u043a\u0435 middleware             next.ServeHTTP(ww, r)         }          \/\/ \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u0432\u044b\u0448\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, \u043f\u0440\u0438\u0432\u0435\u0434\u044f \u0435\u0433\u043e \u043a \u0442\u0438\u043f\u0443 http.HandlerFunc         return http.HandlerFunc(fn)     } }<\/code><\/pre>\n<p>  \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f middleware \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"go\">router.Use(mwLogger.New(log))<\/code><\/pre>\n<p>  \u0415\u0441\u043b\u0438 \u0432\u044b \u0440\u0435\u0448\u0438\u043b\u0438 \u0437\u0430\u0432\u0435\u0441\u0442\u0438 \u0441\u0435\u0431\u0435 \u0442\u0430\u043a\u043e\u0439 middleware, \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0435\u0433\u043e \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0432 internal\/http-server\/middleware.<\/p>\n<p>  <a name=\"6\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>Handlers \u2014 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432<\/h2>\n<p><\/font>  <\/p>\n<h3>Save \u2014 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e URL<\/h3>\n<p>  \u0412\u043e\u0442 \u0438 \u0434\u043e\u0431\u0440\u0430\u043b\u0438\u0441\u044c \u0434\u043e \u0433\u043b\u0430\u0432\u043d\u043e\u0433\u043e \u2014 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. \u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043d\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u0430\u043f\u043a\u0443 internal\/http-server\/handlers\/save \u0438 \u043e\u0434\u043d\u043e\u0438\u043c\u0435\u043d\u043d\u044b\u0439 \u0444\u0430\u0439\u043b save.go.<\/p>\n<p>  \u0417\u0430\u0432\u0435\u0434\u0435\u043c \u0441\u0440\u0430\u0437\u0443 \u0434\u0432\u0435 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u2014 Request \u0438 Response. \u0412 \u043f\u0435\u0440\u0432\u044b\u0439 \u0431\u0443\u0434\u0435\u043c \u0430\u043d\u043c\u0430\u0440\u0448\u0430\u043b\u0438\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441, \u0430 \u0438\u0437 \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0442\u0432\u0435\u0442.<\/p>\n<pre><code class=\"go\">\/\/ internal\/http-server\/handlers\/url\/save\/save.go  type Request struct {     URL   string `json:\"url\" validate:\"required,url\"`     Alias string `json:\"alias,omitempty\"` }  type Response struct {     Status string `json:\"status\"`     Error  string `json:\"error,omitempty\"`     Alias string `json:\"alias\"` }<\/code><\/pre>\n<p>  <i>validate:\u00abrequired,url\u00bb \u2014 \u044d\u0442\u0430 \u0441\u0442\u0440\u043e\u0447\u043a\u0430 \u0434\u043b\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438, \u043e\u0431 \u044d\u0442\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u043d\u0438\u0436\u0435.<\/i><\/p>\n<p>  \u0417\u0430\u0447\u0435\u043c \u0432 \u043e\u0442\u0432\u0435\u0442\u0435 \u043f\u043e\u043b\u0435 Alias: \u0435\u0441\u043b\u0438 \u0432 \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u043e\u043d \u043d\u0435 \u0431\u044b\u043b \u0443\u043a\u0430\u0437\u0430\u043d, \u0442\u043e \u043c\u044b \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0439, \u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0435\u0433\u043e \u0443\u0437\u043d\u0430\u0442\u044c.<\/p>\n<p>  \u041e\u043f\u044b\u0442\u043d\u044b\u0439 \u0433\u043b\u0430\u0437 \u0441\u0440\u0430\u0437\u0443 \u0437\u0430\u043c\u0435\u0442\u0438\u0442 \u0434\u0432\u0430 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0445 \u043f\u043e\u043b\u044f \u2014 Status \u0438 Error. \u041a\u0430\u043a \u0438 \u0432\u043e \u043c\u043d\u043e\u0433\u0438\u0445 \u0434\u0440\u0443\u0433\u0438\u0445 API-\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u0445, \u044d\u0442\u0438 \u043f\u043e\u043b\u044f \u043c\u043e\u0433\u0443\u0442 \u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0432 \u043e\u0442\u0432\u0435\u0442\u0435 \u043b\u044e\u0431\u043e\u0433\u043e \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u0430. \u0410 \u0440\u0430\u0437 \u0442\u0430\u043a, \u0442\u043e \u0438\u043c\u0435\u0435\u0442 \u0441\u043c\u044b\u0441\u043b \u0438\u0445 \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0432 \u043e\u0431\u0449\u0438\u0439 \u043f\u0430\u043a\u0435\u0442. \u0412 \u043c\u043e\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u0442\u0443\u0442: internal\/lib\/api\/response.<\/p>\n<p>  \u0422\u0430\u043a\u0436\u0435 \u044f \u0437\u0430\u0432\u0435\u043b \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0431\u0443\u0434\u0435\u043c \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u043f\u043e\u043b\u0435 Status:<\/p>\n<pre><code class=\"go\">\/\/ internal\/lib\/api\/response\/response.go  type Response struct {     Status string `json:\"status\"`     Error  string `json:\"error,omitempty\"` }  const (     StatusOK    = \"OK\"     StatusError = \"Error\" )<\/code><\/pre>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c Response \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"go\"> \/\/ internal\/http-server\/handlers\/url\/save\/save.go  import (     \/\/ ...      \/\/ \u0434\u043b\u044f \u043a\u0440\u0430\u0442\u043a\u043e\u0441\u0442\u0438 \u0434\u0430\u0435\u043c \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0439 \u0430\u043b\u0438\u0430\u0441 \u043f\u0430\u043a\u0435\u0442\u0443     resp \"url-shortener\/internal\/lib\/api\/response\" )  type Response struct {     resp.Response     Alias string `json:\"alias,omitempty\"` }<\/code><\/pre>\n<p>  \u042d\u0442\u043e\u0442 \u0445\u0435\u043d\u0434\u043b\u0435\u0440 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0435 URL-\u0441\u0442\u0440\u043e\u043a\u0438, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0435\u043c\u0443 \u043d\u0443\u0436\u0435\u043d Storage, \u0430 \u0442\u043e\u0447\u043d\u0435\u0435 \u0435\u0433\u043e \u043c\u0435\u0442\u043e\u0434 \u2014 SaveURL. \u041e\u043f\u0438\u0448\u0435\u043c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441:<\/p>\n<pre><code class=\"go\">type URLSaver interface {     SaveURL(URL, alias string) (int64, error) }<\/code><\/pre>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0441\u0430\u043c\u043e\u043c\u0443 \u0445\u0435\u043d\u0434\u043b\u0435\u0440\u0443. \u0414\u043b\u044f \u0435\u0433\u043e \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u2014 \u0444\u0443\u043d\u043a\u0446\u0438\u044e New.<\/p>\n<pre><code class=\"go\">\/\/ internal\/http-server\/handlers\/url\/save\/save.go  import (     \/\/ ...          \/\/ \u041d\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u044e, \u0447\u0442\u043e \u0442\u0443\u0442 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0430\u043b\u0438\u0430\u0441 \u0434\u043b\u044f \u043a\u0440\u0430\u0442\u043a\u043e\u0441\u0442\u0438     resp \"url-shortener\/internal\/lib\/api\/response\" )  func New(log *slog.Logger, urlSaver URLSaver) http.HandlerFunc {     return func(w http.ResponseWriter, r *http.Request) {         const op = \"handlers.url.save.New\"           \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043a \u0442\u0435\u043a\u0443\u0449\u043c\u0443 \u043e\u0431\u044a\u0435\u043a\u0442\u0443 \u043b\u043e\u0433\u0433\u0435\u0440\u0430 \u043f\u043e\u043b\u044f op \u0438 request_id         \/\/ \u041e\u043d\u0438 \u043c\u043e\u0433\u0443\u0442 \u043e\u0447\u0435\u043d\u044c \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u0442\u044c \u043d\u0430\u043c \u0436\u0438\u0437\u043d\u044c \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c         log = log.With(             slog.String(\"op\", op),             slog.String(\"request_id\", middleware.GetReqID(r.Context())),         )          \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438 \u0430\u043d\u043c\u0430\u0440\u0448\u0430\u043b\u043b\u0438\u043c \u0432 \u043d\u0435\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441         var req Request          err := render.DecodeJSON(r.Body, &amp;req)         if errors.Is(err, io.EOF) {             \/\/ \u0422\u0430\u043a\u0443\u044e \u043e\u0448\u0438\u0431\u043a\u0443 \u0432\u0441\u0442\u0440\u0435\u0442\u0438\u043c, \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u0437\u0430\u043f\u0440\u043e\u0441 \u0441 \u043f\u0443\u0441\u0442\u044b\u043c \u0442\u0435\u043b\u043e\u043c             \/\/ \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u0435\u0451 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e             log.Error(\"request body is empty\")              render.JSON(w, r, render.JSON(w, r, resp.Response{                 Status: resp.StatusError,                 Error:  \"empty request\",             }))              return         }         if err != nil {             log.Error(\"failed to decode request body\", sl.Err(err))              render.JSON(w, r, render.JSON(w, r, resp.Response{                 Status: resp.StatusError,                 Error:  \"failed to decode request\",             }))              return         }          \/\/ \u041b\u0443\u0447\u0448\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u043b\u043e\u0433\u043e\u0432, \u0447\u0435\u043c \u043c\u0435\u043d\u044c\u0448\u0435 - \u043b\u0438\u0448\u043d\u0435\u0435 \u043c\u044b \u043b\u0435\u0433\u043a\u043e \u0441\u043c\u043e\u0436\u0435\u043c \u043f\u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c,         \/\/ \u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438. \u0410 \u0432\u043e\u0442 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043c\u044b \u0443\u0436\u0435 \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u043c.         log.Info(\"request body decoded\", slog.Any(\"req\", req))          \/\/ ...     } }<\/code><\/pre>\n<p>  \u041e\u0431\u044a\u0435\u043a\u0442 urlSaver \u043f\u0435\u0440\u0435\u0434\u0430\u0434\u0438\u043c \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0445\u0435\u043d\u0434\u043b\u0435\u0440\u0430 \u0438\u0437 main.<\/p>\n<p>  \u042d\u0442\u043e\u0442 \u043a\u043e\u0434 \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043a\u0440\u0430\u0441\u0438\u0432\u0435\u0435, \u0435\u0441\u043b\u0438 \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0449\u0438\u0439\u0441\u044f \u043a\u043e\u0434 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043e\u0442\u0432\u0435\u0442\u0430 \u0432 \u043e\u0431\u0449\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e. \u041d\u0430\u043f\u0438\u0448\u0435\u043c \u0435\u0435 \u0432 \u0442\u043e\u043c \u0436\u0435 \u043f\u0430\u043a\u0435\u0442\u0435 response:<\/p>\n<pre><code class=\"go\">\/\/ internal\/lib\/api\/response\/response.go  func Error(msg string) Response {     return Response{         Status: StatusError,         Error:  msg,     } }  func OK() Response {     return Response{         Status: StatusOK,     } }<\/code><\/pre>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u043a\u043e\u0434 \u0432 save.go \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"go\">\/\/ internal\/http-server\/handlers\/url\/save\/save.go  err := render.DecodeJSON(r.Body, &amp;req) if errors.Is(err, io.EOF) {     log.Error(\"request body is empty\")      render.JSON(w, r, resp.Error(\"empty request\")) \/\/ &lt;----      return } if err != nil {     log.Error(\"failed to decode request body\", sl.Err(err))      render.JSON(w, r, resp.Error(\"failed to decode request\")) \/\/ &lt;----      return }<\/code><\/pre>\n<p>  \u0414\u0430\u043b\u0435\u0435 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441. \u041e\u0434\u0438\u043d \u0438\u0437 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432 \u2014 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u0432\u0440\u0443\u0447\u043d\u0443\u044e, \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0432, \u0447\u0442\u043e URL \u2014 \u044d\u0442\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e URL, \u0447\u0442\u043e \u043e\u043d \u043d\u0435 \u043f\u0443\u0441\u0442\u043e\u0439. \u041d\u0430\u0448 \u0441\u0435\u0440\u0432\u0438\u0441 \u043e\u0447\u0435\u043d\u044c \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0439, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0442\u0430\u043a\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430 \u0432\u043f\u043e\u043b\u043d\u0435 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e. \u041d\u043e \u0432 \u043e\u0431\u0449\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043b\u0443\u0447\u0448\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043f\u0430\u043a\u0435\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0438\u043b\u044c\u043d\u043e \u0443\u043f\u0440\u043e\u0449\u0430\u0435\u0442 \u0436\u0438\u0437\u043d\u044c \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <a href=\"https:\/\/github.com\/go-playground\/validator\">go-playground\/validator<\/a>. \u042f \u043f\u043e\u043a\u0430\u0436\u0443, \u043a\u0430\u043a \u0438\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f, \u0430 \u0432\u044b \u0441\u0430\u043c\u0438 \u0440\u0435\u0448\u0430\u0439\u0442\u0435, \u0447\u0442\u043e \u0432\u0430\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f.<\/p>\n<p>  \u0412\u0441\u043f\u043e\u043c\u0438\u043d\u0430\u0435\u043c \u043f\u0440\u043e validate:\u00abrequired,url\u00bb \u0432 \u043e\u0431\u044a\u0435\u043a\u0442\u0435 Request \u2014 \u043e\u043d \u043a\u0430\u043a \u0440\u0430\u0437 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u043e\u043c. \u0414\u043b\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<pre><code class=\"go\">\/\/ internal\/http-server\/handlers\/url\/save\/save.go  func New(log *slog.Logger, urlSaver URLSaver) http.HandlerFunc {     return func(w http.ResponseWriter, r *http.Request) {      \/\/ ...      \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u0430     \/\/ \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0432 \u043d\u0435\u0433\u043e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c     if err := validator.New().Struct(req); err != nil {         \/\/ \u041f\u0440\u0438\u0432\u043e\u0434\u0438\u043c \u043e\u0448\u0438\u0431\u043a\u0443 \u043a \u0442\u0438\u043f\u0443 \u043e\u0448\u0438\u0431\u043a\u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438         validateErr := err.(validator.ValidationErrors)              log.Error(\"invalid request\", sl.Err(err))              render.JSON(w, r, resp.Error(validateErr.Error()))              return     }<\/code><\/pre>\n<p>  \u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u043d\u0435\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0433\u043e \u0432\u0432\u043e\u0434\u0430 \u0434\u0430\u043d\u043d\u044b\u0445, \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0442\u0430\u043a\u043e\u0439 \u043e\u0442\u0432\u0435\u0442:<\/p>\n<pre><code class=\"json\">{     \"status\": \"Error\",     \"error\": \"Key: 'Request.URL' Error:Field validation for 'URL' failed on the 'url' tag\" }<\/code><\/pre>\n<p>  \u041c\u043e\u0436\u0435\u0442\u0435 \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a, \u043d\u043e \u043c\u043d\u0435 \u0442\u0430\u043a\u043e\u0439 \u043e\u0442\u0432\u0435\u0442 \u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u2014 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043d\u0435\u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u0447\u0442\u043e \u0442\u0443\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043e. \u0414\u043b\u044f \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0431\u043e\u043b\u0435\u0435 \u044f\u0441\u043d\u043e\u0433\u043e \u043e\u0442\u0432\u0435\u0442\u0430 \u043b\u0443\u0447\u0448\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u043f\u0430\u043a\u0435\u0442 response \u0442\u0430\u043a\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e:<\/p>\n<pre><code class=\"go\">\/\/ internal\/lib\/api\/response\/response.go  func ValidationError(errs validator.ValidationErrors) Response {     var errMsgs []string      for _, err := range errs {         switch err.ActualTag() {         case \"required\":             errMsgs = append(errMsgs, fmt.Sprintf(\"field %s is a required field\", err.Field()))         case \"url\":             errMsgs = append(errMsgs, fmt.Sprintf(\"field %s is not a valid URL\", err.Field()))         default:             errMsgs = append(errMsgs, fmt.Sprintf(\"field %s is not valid\", err.Field()))         }     }      return Response{         Status: StatusError,         Error:  strings.Join(errMsgs, \", \"),     } }<\/code><\/pre>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0432\u0435\u0440\u043d\u0435\u0442 \u0432\u043d\u044f\u0442\u043d\u044b\u0439 \u043e\u0442\u0432\u0435\u0442:<\/p>\n<pre><code class=\"go\">render.JSON(w, r, resp.ValidationError(validateErr)) {     \"status\": \"Error\",     \"error\": \"field URL is not a valid URL\" }<\/code><\/pre>\n<p>  Alias \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0432\u0440\u0443\u0447\u043d\u0443\u044e. \u0415\u0441\u043b\u0438 \u043e\u043d \u043f\u0443\u0441\u0442\u043e\u0439 \u2014 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0439:<\/p>\n<pre><code class=\"go\">\/\/ internal\/http-server\/handlers\/url\/save\/save.go  \/\/ TODO: move to config when needed const aliasLength = 6  func New(log *slog.Logger, urlSaver URLSaver) http.HandlerFunc {     return func(w http.ResponseWriter, r *http.Request) {         \/\/ ...              alias := req.Alias         if alias == \"\" {             alias = random.NewRandomString(aliasLength)         }     } }<\/code><\/pre>\n<p>  \u0422\u0443\u0442 \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0443 \u0441\u0432\u043e\u0438\u043c\u0438 \u043c\u0435\u0442\u043e\u0434\u0430\u043c\u0438 \u043b\u0438\u0431\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0433\u043e\u0442\u043e\u0432\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443. \u042f \u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e random, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u0444\u0443\u043d\u043a\u0446\u0438\u044e NewRandomString:<\/p>\n<pre><code class=\"go\">\/\/ internal\/lib\/random\/random.go  \/\/ NewRandomString generates random string with given size. func NewRandomString(size int) string {     rnd := rand.New(rand.NewSource(time.Now().UnixNano()))      chars := []rune(\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\" +         \"abcdefghijklmnopqrstuvwxyz\" +         \"0123456789\")      b := make([]rune, size)     for i := range b {         b[i] = chars[rnd.Intn(len(chars))]     }      return string(b) }<\/code><\/pre>\n<p>  <\/p>\n<blockquote><p><i>\u0421\u043e\u0432\u0435\u0442\u0443\u044e \u043f\u043e\u043a\u0440\u044b\u0442\u044c \u044d\u0442\u0443 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0442\u0435\u0441\u0442\u0430\u043c\u0438 \u0438 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0443\u043f\u0440\u0430\u0436\u043d\u0435\u043d\u0438\u044f, \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e. \u0415\u0441\u043b\u0438 \u0432\u0441\u0435 \u0436\u0435 \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043c\u043e\u0438 \u0442\u0435\u0441\u0442\u044b, \u043e\u043d\u0438 \u0431\u0443\u0434\u0443\u0442 \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430.<\/i><\/p><\/blockquote>\n<p>  \u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c URL \u0438 Alias, \u0430 \u043f\u043e\u0441\u043b\u0435 \u2014 \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043e\u0442\u0432\u0435\u0442 \u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\u043c \u043e\u0431 \u0443\u0441\u043f\u0435\u0445\u0435.<\/p>\n<pre><code class=\"go\">\/\/ internal\/http-server\/handlers\/url\/save\/save.go  func New(log *slog.Logger, urlSaver URLSaver) http.HandlerFunc {     return func(w http.ResponseWriter, r *http.Request) {         \/\/ ...          id, err := urlSaver.SaveURL(req.URL, alias)         if errors.Is(err, storage.ErrURLExists) {             \/\/ \u041e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u044e,             \/\/ \u043a\u043e\u0433\u0434\u0430 \u0437\u0430\u043f\u0438\u0441\u044c \u0441 \u0442\u0430\u043a\u0438\u043c Alias \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442             log.Info(\"url already exists\", slog.String(\"url\", req.URL))              render.JSON(w, r, resp.Error(\"url already exists\"))              return         }         if err != nil {             log.Error(\"failed to add url\", sl.Err(err))              render.JSON(w, r, resp.Error(\"failed to add url\"))              return         }          log.Info(\"url added\", slog.Int64(\"id\", id))          responseOK(w, r, alias)     } }<\/code><\/pre>\n<p>  \u0424\u0443\u043d\u043a\u0446\u0438\u044e responseOK \u043e\u043f\u0438\u0448\u0435\u043c \u0432 \u044d\u0442\u043e\u043c \u0436\u0435 \u0444\u0430\u0439\u043b\u0435:<\/p>\n<pre><code class=\"go\">\/\/ internal\/http-server\/handlers\/url\/save\/save.go  func responseOK(w http.ResponseWriter, r *http.Request, alias string) {     render.JSON(w, r, Response{         Response: resp.OK(),         Alias:    alias,     }) }<\/code><\/pre>\n<p>  \u0421\u0443\u043f\u0435\u0440 \u2014 \u0445\u0435\u043d\u0434\u043b\u0435\u0440 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043d\u0430\u043f\u0438\u0441\u0430\u043d. \u0415\u0441\u043b\u0438 \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0435\u0433\u043e \u043a\u043e\u0434 \u0446\u0435\u043b\u0438\u043a\u043e\u043c, \u043c\u043e\u0436\u0435\u0442\u0435 \u0437\u0430\u0433\u043b\u044f\u043d\u0443\u0442\u044c \u0432 <a href=\"https:\/\/github.com\/GolangLessons\/url-shortener\">\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/a>.<\/p>\n<p>  \u0427\u0442\u043e\u0431\u044b \u0432\u0441\u0435 \u044d\u0442\u043e \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0442\u0435\u0441\u0442 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043f\u0430\u043a\u0435\u0442\u0430 httptest \u0438\u0437 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438. \u0418 \u0432\u043c\u0435\u0441\u0442\u043e \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u0433\u043e Storage \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Mock (\u043c\u043e\u043a). \u041d\u0430 \u044d\u0442\u0443 \u0442\u0435\u043c\u0443 \u0443 \u043c\u0435\u043d\u044f \u0442\u0430\u043a\u0436\u0435 <a href=\"https:\/\/youtu.be\/qaaa3RsC0FQ\">\u0435\u0441\u0442\u044c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u044b\u0439 \u0440\u043e\u043b\u0438\u043a<\/a> \u2014 \u0442\u0430\u043c \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u044e \u043f\u0440\u043e \u0441\u0443\u0442\u044c \u043c\u043e\u043a\u043e\u0432 \u0438 \u0438\u0445 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044e.<\/p>\n<p>  \u0414\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043c\u043e\u043a\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/github.com\/vektra\/mockery\">mockery<\/a>, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u0440\u044f\u0434\u043e\u043c \u0441 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0432\u043e\u0442 \u0442\u0430\u043a\u0443\u044e \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u044e:<\/p>\n<pre><code class=\"go\">\/\/go:generate go run github.com\/vektra\/mockery\/v2@v2.28.2 --name=URLSaver type URLSaver interface {     SaveURL(URL, alias string) (int64, error) }<\/code><\/pre>\n<p>  \u041f\u043e\u0441\u043b\u0435 \u2014 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0441\u0430\u043c \u043c\u043e\u043a \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u044b:<\/p>\n<pre><code class=\"bash\">.\/internal\/http-server\/handlers\/url\/save\/save.go<\/code><\/pre>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0441\u0430\u043c \u0442\u0435\u0441\u0442. \u0420\u044f\u0434\u043e\u043c \u0441 \u0444\u0430\u0439\u043b\u043e\u043c save.go \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u2014 save_test.go. \u0422\u0430\u043c \u0443 \u043d\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0442\u0430\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u0442\u0435\u0441\u0442:<\/p>\n<pre><code class=\"go\">\/\/ internal\/http-server\/handlers\/url\/save\/save_test.go func TestSaveHandler(t *testing.T) {     cases := []struct {         name      string \/\/ \u0418\u043c\u044f \u0442\u0435\u0441\u0442\u0430         alias     string \/\/ \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u0439 alias         url       string \/\/ \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u0439 URL         respError string \/\/ \u041a\u0430\u043a\u0443\u044e \u043e\u0448\u0438\u0431\u043a\u0443 \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c?         mockError error  \/\/ \u041e\u0448\u0438\u0431\u043a\u0443, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0432\u0435\u0440\u043d\u0451\u0442 \u043c\u043e\u043a     }{         {             name:  \"Success\",             alias: \"test_alias\",             url:   \"https:\/\/google.com\",             \/\/ \u0422\u0443\u0442 \u043f\u043e\u043b\u044f respError \u0438 mockError \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u0443\u0441\u0442\u044b\u043c\u0438,             \/\/ \u0442.\u043a. \u044d\u0442\u043e \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441         },         \/\/ \u0414\u0440\u0443\u0433\u0438\u0435 \u043a\u0435\u0439\u0441\u044b ...     }      for _, tc := range cases {           t.Run(tc.name, func(t *testing.T) {             \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u043c\u043e\u043a\u0430 \u0441\u0442\u043e\u0440\u0430\u0434\u0436\u0430             urlSaverMock := mocks.NewURLSaver(t)              \/\/ \u0415\u0441\u043b\u0438 \u043e\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0439 \u043e\u0442\u0432\u0435\u0442, \u0437\u043d\u0430\u0447\u0438\u0442 \u043a \u043c\u043e\u043a\u0443 \u0442\u043e\u0447\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0437\u043e\u0432             \/\/ \u041b\u0438\u0431\u043e \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0432 \u043e\u0442\u0432\u0435\u0442\u0435 \u043e\u0436\u0438\u0434\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443,             \/\/ \u043d\u043e \u043c\u043e\u043a \u0434\u043e\u043b\u0436\u0435\u043d \u043e\u0442\u0432\u0435\u0442\u0438\u0442\u044c \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439, \u043a \u043d\u0435\u043c\u0443 \u0442\u043e\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441:             if tc.respError == \"\" || tc.mockError != nil {                 \/\/ \u0421\u043e\u043e\u0431\u0449\u0430\u0435\u043c \u043c\u043e\u043a\u0443, \u043a\u0430\u043a\u043e\u0439 \u043a \u043d\u0435\u043c\u0443 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441, \u0438 \u0447\u0442\u043e \u043d\u0430\u0434\u043e \u0432\u0435\u0440\u043d\u0443\u0442\u044c                 urlSaverMock.On(\"SaveURL\", tc.url, mock.AnythingOfType(\"string\")).                     Return(int64(1), tc.mockError).                     Once() \/\/ \u0417\u0430\u043f\u0440\u043e\u0441 \u0431\u0443\u0434\u0435\u0442 \u0440\u043e\u0432\u043d\u043e \u043e\u0434\u0438\u043d             }              \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u0430\u0448 \u0445\u044d\u043d\u0434\u043b\u0435\u0440             handler := save.New(sl.NewDiscardLogger(), urlSaverMock)              \/\/ \u0424\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0442\u0435\u043b\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430             input := fmt.Sprintf(`{\"url\": \"%s\", \"alias\": \"%s\"}`, tc.url, tc.alias)              \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u0437\u0430\u043f\u0440\u043e\u0441\u0430             req, err := http.NewRequest(http.MethodPost, \"\/save\", bytes.NewReader([]byte(input)))             require.NoError(t, err)              \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c ResponseRecorder \u0434\u043b\u044f \u0437\u0430\u043f\u0438\u0441\u0438 \u043e\u0442\u0432\u0435\u0442\u0430 \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u0430             rr := httptest.NewRecorder()             \/\/ \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441, \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u044f \u043e\u0442\u0432\u0435\u0442 \u0432 \u0440\u0435\u043a\u043e\u0440\u0434\u0435\u0440             handler.ServeHTTP(rr, req)              \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u0441\u0442\u0430\u0442\u0443\u0441 \u043e\u0442\u0432\u0435\u0442\u0430 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u0439             require.Equal(t, rr.Code, http.StatusOK)              body := rr.Body.String()              var resp save.Response              \/\/ \u0410\u043d\u043c\u0430\u0440\u0448\u0430\u043b\u043b\u0438\u043c \u0442\u0435\u043b\u043e, \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0447\u0442\u043e \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043d\u0435 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u043e \u043e\u0448\u0438\u0431\u043e\u043a             require.NoError(t, json.Unmarshal([]byte(body), &amp;resp))              \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u043c\u043e\u0439 \u043e\u0448\u0438\u0431\u043a\u0438 \u0432 \u043e\u0442\u0432\u0435\u0442\u0435             require.Equal(t, tc.respError, resp.Error)              \/\/ \u0414\u0440\u0443\u0433\u0438\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438         })     } }<\/code><\/pre>\n<p>  <\/p>\n<blockquote><p><i>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0443\u043f\u0440\u0430\u0436\u043d\u0435\u043d\u0438\u044f, \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u043a\u0435\u0439\u0441\u043e\u0432 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043e\u043a \u0432 \u044d\u0442\u043e\u0442 \u0442\u0435\u0441\u0442. \u0412 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u0441\u043a\u0430\u0436\u0435\u0442\u0435 \u0441\u0435\u0431\u0435 \u0441\u043f\u0430\u0441\u0438\u0431\u043e, \u0435\u0441\u043b\u0438 \u0431\u0443\u0434\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0432\u0448\u0438\u0439\u0441\u044f \u0441\u0435\u0440\u0432\u0438\u0441. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u044d\u0442\u043e \u043d\u0435\u043f\u043b\u043e\u0445\u0430\u044f \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0430.<\/i><\/p><\/blockquote>\n<p>  \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u0441\u044f \u0432 main \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043d\u0430\u0448 \u043f\u0435\u0440\u0432\u044b\u0439 \u0445\u0435\u043d\u0434\u043b\u0435\u0440 \u0432 \u0440\u043e\u0443\u0442\u0435\u0440:<\/p>\n<pre><code class=\"go\">router.Post(\"\/\", save.New(log, storage))<\/code><\/pre>\n<p>  \u041d\u0430 \u044d\u0442\u043e\u043c \u044d\u0442\u0430\u043f\u0435 \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0441\u044f \u0438 \u043f\u043e\u0438\u0433\u0440\u0430\u0442\u044c\u0441\u044f \u0441 \u043f\u043e\u043b\u0443\u0447\u0438\u0432\u0448\u0438\u043c\u0441\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u043c. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u043e\u043d \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0432 \u043d\u0435\u0433\u043e \u00ab\u0447\u0435\u0441\u0442\u043d\u044b\u0435\u00bb \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0447\u0435\u0440\u0435\u0437 Postman.<\/p>\n<h3>Redirect \u2014 \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0439 URL<\/h3>\n<p>  \u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u0445\u0435\u043d\u0434\u043b\u0435\u0440\u0443 \u2014 redirect. \u042d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 GET-\u0437\u0430\u043f\u0440\u043e\u0441, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043e\u0431\u044a\u0435\u043a\u0442 Request \u0437\u0434\u0435\u0441\u044c \u043d\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f, \u043a\u0430\u043a \u0438 Response. \u0412\u0435\u0434\u044c \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u043c\u044b \u0442\u043e\u0436\u0435 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0431\u0443\u0434\u0435\u043c, \u0430 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0440\u0435\u0434\u0438\u0440\u0435\u043a\u0442. \u041a\u043e\u0434 \u0445\u0435\u043d\u0434\u0435\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u0442\u0430\u043a\u0438\u043c:<\/p>\n<pre><code class=\"go\">\/\/ cmd\/url-shortener\/main.go  \/\/go:generate go run github.com\/vektra\/mockery\/v2@v2.28.2 --name=URLGetter \/\/ \/\/ URLGetter is an interface for getting url by alias. type URLGetter interface {     GetURL(alias string) (string, error) }  func New(log *slog.Logger, urlGetter URLGetter) http.HandlerFunc {     return func(w http.ResponseWriter, r *http.Request) {         const op = \"handlers.url.redirect.New\"          log = log.With(             slog.String(\"op\", op),             slog.String(\"request_id\", middleware.GetReqID(r.Context())),         )          \/\/ \u0420\u043e\u0443\u0442\u0435\u0440 chi \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0434\u0435\u043b\u0430\u0442\u044c \u0432\u043e\u0442 \u0442\u0430\u043a\u0438\u0435 \u0444\u0438\u043d\u0442\u044b -         \/\/ \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c GET-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u043e \u0438\u0445 \u0438\u043c\u0435\u043d\u0430\u043c.         \/\/ \u0418\u043c\u0435\u043d\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u043f\u0440\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u0430 \u0432 \u0440\u043e\u0443\u0442\u0435\u0440, \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043d\u0438\u0436\u0435.         alias := chi.URLParam(r, \"alias\")         if alias == \"\" {             log.Info(\"alias is empty\")              render.JSON(w, r, resp.Error(\"not found\"))              return         }          \/\/ \u041d\u0430\u0445\u043e\u0434\u0438\u043c URL \u043f\u043e \u0430\u043b\u0438\u0430\u0441\u0443 \u0432 \u0411\u0414         resURL, err := urlGetter.GetURL(alias)         if errors.Is(err, storage.ErrURLNotFound) {             \/\/ \u041d\u0435 \u043d\u0430\u0448\u043b\u0438 URL, \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u043c \u043e\u0431 \u044d\u0442\u043e\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0443             log.Info(\"url not found\", \"alias\", alias)              render.JSON(w, r, resp.Error(\"not found\"))              return         }         if err != nil {             \/\/ \u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0438\u0442\u044c \u043f\u043e\u0438\u0441\u043a             log.Error(\"failed to get url\", sl.Err(err))              render.JSON(w, r, resp.Error(\"internal error\"))              return         }          log.Info(\"got url\", slog.String(\"url\", resURL))          \/\/ \u0414\u0435\u043b\u0430\u0435\u043c \u0440\u0435\u0434\u0438\u0440\u0435\u043a\u0442 \u043d\u0430 \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0439 URL         http.Redirect(w, r, resURL, http.StatusFound)     } }<\/code><\/pre>\n<p>  \u0412 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0439 \u0441\u0442\u0440\u043e\u0447\u043a\u0435 \u0434\u0435\u043b\u0430\u0435\u043c \u0440\u0435\u0434\u0438\u0440\u0435\u043a\u0442 \u0441\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043c http.StatusFound \u2014 \u043a\u043e\u0434 HTTP 302. \u041e\u043d \u043e\u0431\u044b\u0447\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0439, \u0430 \u043d\u0435 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u044b\u0445, \u0437\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 301.<\/p>\n<p>  \u041d\u0430\u0448 \u0441\u0435\u0440\u0432\u0438\u0441 \u043c\u043e\u0436\u0435\u0442 \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0435 URL \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u0438 (\u043c\u044b \u0432\u0435\u0434\u044c \u043c\u043e\u0436\u0435\u043c \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u043b\u0438 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0439 URL), \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0435\u0441\u0442\u044c \u0441\u043c\u044b\u0441\u043b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u043c\u0435\u043d\u043d\u043e http.StatusFound. \u042d\u0442\u043e \u0432\u0430\u0436\u043d\u043e \u0434\u043b\u044f \u0441\u0438\u0441\u0442\u0435\u043c \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0445 \u043c\u0430\u0448\u0438\u043d \u2014 \u043e\u043d\u0438 \u043e\u0431\u044b\u0447\u043d\u043e \u043a\u044d\u0448\u0438\u0440\u0443\u044e\u0442 \u0440\u0435\u0434\u0438\u0440\u0435\u043a\u0442\u044b \u0441 \u043a\u043e\u0434\u043e\u043c 301, \u0442\u043e \u0435\u0441\u0442\u044c \u0441\u0447\u0438\u0442\u0430\u044e\u0442 \u0438\u0445 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u044b\u043c\u0438. \u041d\u0430\u043c \u0442\u0430\u043a\u043e\u0435 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e.<\/p>\n<p>  \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u043d\u043e\u0432\u044b\u0439 \u0445\u0435\u043d\u0434\u043b\u0435\u0440 \u0432 main:<\/p>\n<pre><code class=\"go\">router.Get(\"\/{alias}\", redirect.New(log, storage))<\/code><\/pre>\n<p>  <\/p>\n<blockquote><p><i>\u0417\u0434\u0435\u0441\u044c \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u043f\u0443\u0442\u044c \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0438 \u0438\u043c\u0435\u043d\u0443\u0435\u043c \u0435\u0433\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u2014 {alias}. \u0412 \u0445\u0435\u043d\u0434\u043b\u0435\u0440\u0435 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u044d\u0442\u043e\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043f\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u043c\u0443 \u0438\u043c\u0435\u043d\u0438, \u0447\u0442\u043e \u043c\u044b \u0438 \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u0432\u044b\u0448\u0435.<\/i><\/p><\/blockquote>\n<p>  \u042d\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0443\u0434\u043e\u0431\u043d\u0430\u044f \u0438 \u0433\u0438\u0431\u043a\u0430\u044f \u0448\u0442\u0443\u043a\u0430. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0431\u043e\u043b\u0435\u0435 \u0441\u043b\u043e\u0436\u043d\u044b\u0435 \u043f\u0443\u0442\u0438, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<pre><code class=\"go\">router.Get(\"\/v1\/{user_id}\/uid\", redirect.New(log, storage))<\/code><\/pre>\n<p>  \u041f\u0440\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u0432\u0438\u0434\u0430 \/v1\/1234\/uid \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0437\u0432\u043b\u0435\u0447\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 1234 \u043f\u043e \u0438\u043c\u0435\u043d\u0438 user_id. \u0415\u0441\u043b\u0438 \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u0444\u043e\u0440\u043c\u0430\u0442 \u043f\u0443\u0442\u0438 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u0441\u044f, \u043d\u0430 \u043a\u043e\u0434 \u0445\u0435\u043d\u0434\u043b\u0435\u0440\u0430 \u044d\u0442\u043e \u043d\u0438\u043a\u0430\u043a \u043d\u0435 \u043f\u043e\u0432\u043b\u0438\u044f\u0435\u0442. \u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u2014 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0438\u043c\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430.<\/p>\n<p>  \u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0432\u0430\u043c \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430, \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0433\u043e \u0443\u043f\u0440\u0430\u0436\u043d\u0435\u043d\u0438\u044f. \u041b\u0438\u0431\u043e \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043c\u043e\u0438 \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430.<\/p>\n<p>  \u0422\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 URL. \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0435\u0433\u043e \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0442\u0430\u043a:<\/p>\n<pre><code class=\"go\">r.Delete(\"\/{alias}\", remove.New(log, storage))<\/code><\/pre>\n<p>  \u041f\u0443\u0442\u044c \u0431\u0443\u0434\u0435\u0442 \u043a\u0430\u043a \u0443 \u0440\u0435\u0434\u0438\u0440\u0435\u043a\u0442\u0430, \u043d\u043e \u0442\u0438\u043f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u2014 DELETE. \u042d\u0442\u043e \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u0434\u043b\u044f REST API. \u041b\u0438\u0431\u043e \/url\/{alias}, \u0435\u0441\u043b\u0438 \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u044f\u0442\u044c \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438, \u043a\u0440\u043e\u043c\u0435 URL.<\/p>\n<p>  <a name=\"7\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f<\/h2>\n<p><\/font><br \/>  \u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0433\u043e\u0442\u043e\u0432, \u043d\u043e \u0435\u0433\u043e \u0440\u0443\u0447\u043a\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u044b \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439. \u0415\u0441\u043b\u0438 \u043c\u044b \u043f\u0438\u0448\u0435\u043c \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u043b\u044f \u043b\u0438\u0447\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f, \u0442\u043e \u043c\u044b \u044d\u0442\u043e\u0433\u043e, \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u0436\u0435, \u043d\u0435 \u0445\u043e\u0442\u0438\u043c. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0443\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u0434\u043b\u044f \u0440\u0443\u0447\u043a\u0438 save. \u041d\u0443 \u0438 \u0434\u043b\u044f remove\/delete, \u0435\u0441\u043b\u0438 \u0432\u044b \u0442\u0430\u043a\u0443\u044e \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438.<\/p>\n<p>  \u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0441\u0430\u043c\u0443\u044e \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0443\u044e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Authentication\">HTTP Basic Auth<\/a> \u2014 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0443\u044e \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u043f\u043e \u043b\u043e\u0433\u0438\u043d\u0443 \u0438 \u043f\u0430\u0440\u043e\u043b\u044e. \u0415\u0441\u043b\u0438 \u0437\u0430\u0445\u043e\u0442\u0438\u0442\u0435 \u0432\u044b\u0434\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u044b \u0441\u0432\u043e\u0438\u043c \u0434\u0440\u0443\u0437\u044c\u044f\u043c, \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0437\u0430\u0432\u0435\u0441\u0442\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u0430\u0440 \u00ab\u043b\u043e\u0433\u0438\u043d-\u043f\u0430\u0440\u043e\u043b\u044c\u00bb \u2014 \u044d\u0442\u043e \u043d\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430. \u041d\u043e \u0435\u0441\u043b\u0438 \u0432\u044b \u0440\u0435\u0448\u0438\u0442\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0436\u0435\u043b\u0430\u044e\u0449\u0438\u0445, \u043b\u0443\u0447\u0448\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0441\u0435\u0440\u044c\u0435\u0437\u043d\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u2014 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u0441 \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u043c \u043f\u0440\u0430\u0432 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u0444\u0438\u0447\u0430\u043c\u0438. \u041b\u0438\u0431\u043e \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0437\u044f\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435.<\/p>\n<p>  \u041f\u0430\u0440\u0443 \u043b\u043e\u0433\u0438\u043d-\u043f\u0430\u0440\u043e\u043b\u044c (\u043a\u0440\u0435\u0434\u044b, credentials) \u0431\u0443\u0434\u0435\u043c \u0431\u0440\u0430\u0442\u044c \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041d\u0435 \u043f\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0439\u0442\u0435, \u043c\u044b \u041d\u0415 \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c \u0432 \u043e\u0431\u0449\u0435\u043c \u043a\u043e\u043d\u0444\u0438\u0433\u0435. \u041f\u0440\u0438 \u0434\u0435\u043f\u043b\u043e\u0435 \u0431\u0443\u0434\u0435\u043c \u0435\u0433\u043e \u043f\u0440\u043e\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u0441\u0435\u043a\u0440\u0435\u0442\u044b GitHub Actions.<\/p>\n<p>  \u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0432 \u043e\u0431\u044a\u0435\u043a\u0442 \u043a\u043e\u043d\u0444\u0438\u0433\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043f\u043e\u043b\u044f User \u0438 Password:<\/p>\n<pre><code class=\"go\">\/\/ internal\/config\/config.go  type HTTPServer struct {     Address     string        `yaml:\"address\" env-default:\"0.0.0.0:8080\"`     Timeout     time.Duration `yaml:\"timeout\" env-default:\"5s\"`     IdleTimeout time.Duration `yaml:\"idle_timeout\" env-default:\"60s\"`     \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c:     User        string        `yaml:\"user\" env-required:\"true\"`     Password    string        `yaml:\"password\" env-required:\"true\" env:\"HTTP_SERVER_PASSWORD\"` }<\/code><\/pre>\n<p>  \u0412 \u0441\u0432\u043e\u0439 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u043d\u0444\u0438\u0433 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u0440\u0435\u0434\u044b \u0432 \u044f\u0432\u043d\u043e\u043c \u0432\u0438\u0434\u0435:<\/p>\n<pre><code class=\"plaintext\"># config\/local.yaml  env: \"local\" storage_path: \".\/storage\/storage.db\" http_server:   address: \"localhost:8082\"   timeout: 4s   idle_timeout: 30s   user: \"my_user\"   password: \"my_pass\"<\/code><\/pre>\n<p>  <\/p>\n<blockquote><p><i>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435: \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d-\u043a\u043e\u043d\u0444\u0438\u0433 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u043b\u043e\u0433\u0438\u043d. \u041f\u0430\u0440\u043e\u043b\u044c \u0432\u0430\u0436\u043d\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u2014 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u043f\u0440\u043e \u0434\u0435\u043f\u043b\u043e\u0439.<\/i><\/p><\/blockquote>\n<p>  \u0412 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 main \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u043c \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044e \u0445\u0435\u043d\u0434\u043b\u0435\u0440\u043e\u0432 \u0432 \u0440\u043e\u0443\u0442\u0435\u0440\u0435. \u0414\u043b\u044f \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u044b\u0445 \u0445\u0435\u043d\u0434\u043b\u0435\u0440\u043e\u0432 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0439 \u0440\u043e\u0443\u0442\u0435\u0440, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c middleware \u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439 (\u043e\u043d \u0438\u0434\u0435\u0442 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043f\u0430\u043a\u0435\u0442\u043e\u043c chi).<\/p>\n<pre><code class=\"go\">\/\/ cmd\/url-shortener\/main.go  \/\/ \u0412\u0441\u0435 \u043f\u0443\u0442\u0438 \u044d\u0442\u043e\u0433\u043e \u0440\u043e\u0443\u0442\u0435\u0440\u0430 \u0431\u0443\u0434\u0443\u0442 \u043d\u0430\u0447\u0438\u043d\u0430\u0442\u044c\u0441\u044f \u0441 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u0430 `\/url` router.Route(\"\/url\", func(r chi.Router) {     \/\/ \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e     r.Use(middleware.BasicAuth(\"url-shortener\", map[string]string{         \/\/ \u041f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0432 middleware \u043a\u0440\u0435\u0434\u044b         cfg.HTTPServer.User: cfg.HTTPServer.Password,         \/\/ \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0431\u043e\u043b\u0435\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f,         \/\/ \u0442\u043e \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u0440\u044b \u043f\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0438.     }))      r.Post(\"\/\", save.New(log, storage)) })  \/\/ \u0425\u044d\u043d\u0434\u043b\u0435\u0440 redirect \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u0441\u043d\u0430\u0440\u0443\u0436\u0438, \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u0440\u043e\u0443\u0442\u0435\u0440\u0435 router.Get(\"\/{alias}\", redirect.New(log, storage))<\/code><\/pre>\n<p>  <a name=\"8\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b<\/h2>\n<p><\/font><br \/>  \u0420\u0430\u043d\u0435\u0435 \u043c\u044b \u0443\u0436\u0435 \u043f\u0438\u0441\u0430\u043b\u0438 \u0442\u0435\u0441\u0442\u044b, \u043d\u043e \u043e\u043d\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u0443\u0441\u043e\u0447\u043a\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u044f \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u0430\u0436\u043d\u044b\u0435 \u044d\u0442\u0430\u043f\u044b. \u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u0442\u0435\u0441\u0442\u044b \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u043e\u0432 \u043d\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e\u0442 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u0430 \u044d\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0432\u0430\u0436\u043d\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442.<\/p>\n<p>  \u0414\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u0441\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0446\u0435\u043b\u0438\u043a\u043e\u043c \u0438\u043c\u0435\u0435\u0442 \u0441\u043c\u044b\u0441\u043b \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u0435\u0433\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u043d\u0435\u043a\u0443\u044e \u0447\u0435\u0440\u043d\u0443\u044e \u043a\u043e\u0440\u043e\u0431\u043a\u0443. \u0421\u0435\u0440\u0432\u0438\u0441 \u0431\u0443\u0434\u0435\u0442 \u0447\u0435\u0441\u0442\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c\u0441\u044f \u0438 \u043d\u0435 \u043f\u043e\u0434\u043e\u0437\u0440\u0435\u0432\u0430\u0442\u044c, \u0447\u0442\u043e \u0435\u0433\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u044e\u0442.<\/p>\n<p>  \u0418\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f \u0432 \u0434\u043e\u043a\u0435\u0440\u0435, \u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438, \u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438, \u0437\u0430\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f \u0441\u0435\u0442\u0435\u0432\u044b\u043c\u0438 \u043c\u043e\u043a\u0430\u043c\u0438. \u0417\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0443 \u043c\u0435\u043d\u044f \u043f\u043e\u043a\u0430 \u043d\u0435\u0442, \u043d\u043e \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u044f \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u044b\u0439 \u0440\u043e\u043b\u0438\u043a \u043f\u0440\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b, \u0433\u0434\u0435 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043d \u0438 \u044d\u0442\u043e\u0442 \u043c\u043e\u043c\u0435\u043d\u0442 (\u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0439\u0442\u0435\u0441\u044c \u043d\u0430 <a href=\"https:\/\/www.youtube.com\/@nikolay_tuzov\">\u043c\u043e\u0439 \u043a\u0430\u043d\u0430\u043b<\/a>, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c).<\/p>\n<p>  \u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0434\u0432\u0435 \u043d\u043e\u0432\u044b\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u0447\u0435\u043d\u044c \u0443\u043f\u0440\u043e\u0449\u0430\u044e\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/gavv\/httpexpect\">httpexpect<\/a> \u2014 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f REST API, <\/li>\n<li><a href=\"https:\/\/github.com\/brianvoe\/gofakeit\">gofakeit<\/a> \u2014 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0430\u0437\u043d\u043e\u0433\u043e \u0444\u043e\u0440\u043c\u0430\u0442\u0430 (\u0438\u043c\u0435\u043d\u0430, \u0438\u043c\u0435\u0439\u043b\u044b, \u043d\u043e\u043c\u0435\u0440\u0430 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043e\u0432, URL \u0438 \u0434\u0440\u0443\u0433\u043e\u0435).<\/li>\n<\/ul>\n<p>  \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u0438\u0445:<\/p>\n<pre><code class=\"bash\">> go get github.com\/brianvoe\/gofakeit\/v6 > go get github.com\/gavv\/httpexpect\/v2<\/code><\/pre>\n<p>  \u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0431\u0443\u0434\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0449\u0430\u0442\u044c \u0432 \u043f\u0430\u043f\u043a\u0435 tests, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0430 \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432 \u043d\u0435\u0439 \u0444\u0430\u0439\u043b url_shortener_test.go \u0438 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0441\u0430\u043c\u044b\u0439 \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u0438\u0439 \u0442\u0435\u0441\u0442 Happy Path:<\/p>\n<pre><code class=\"go\">\/\/ tests\/url_shortener_test.go  const (     host = \"localhost:8082\" )  func TestURLShortener_HappyPath(t *testing.T) {     \/\/ \u0423\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f URL     u := url.URL{         Scheme: \"http\",         Host:   host,     }      \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442 httpexpect     e := httpexpect.Default(t, u.String())      e.POST(\"\/url\"). \/\/ \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c POST-\u0437\u0430\u043f\u0440\u043e\u0441, \u043f\u0443\u0442\u044c - '\/url'         WithJSON(save.Request{ \/\/ \u0424\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0442\u0435\u043b\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430             URL:   gofakeit.URL(), \/\/ \u0413\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0439 URL             Alias: random.NewRandomString(10), \/\/ \u0413\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443         }).         WithBasicAuth(\"myuser\", \"mypass\"). \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043a \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043a\u0440\u0435\u0434\u044b \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438         Expect(). \/\/ \u0414\u0430\u043b\u0435\u0435 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u044f\u0435\u043c \u043d\u0430\u0448\u0438 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f \u043e\u0442 \u043e\u0442\u0432\u0435\u0442\u0430         Status(200). \/\/ \u041a\u043e\u0434 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c 200         JSON().Object(). \/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c JSON-\u043e\u0431\u044a\u0435\u043a\u0442 \u0442\u0435\u043b\u0430 \u043e\u0442\u0432\u0435\u0442\u0430         ContainsKey(\"alias\") \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u0432 \u043d\u0451\u043c \u0435\u0441\u0442\u044c \u043a\u043b\u044e\u0447 'alias' }<\/code><\/pre>\n<p>  \u0422\u0435\u0441\u0442 \u0434\u0435\u043b\u0430\u0435\u0442 \u043b\u0438\u0448\u044c \u0441\u0430\u043c\u044b\u0435 \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438, \u0438 \u044f \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0432\u043e\u0438. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u043d\u0430\u043c \u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u0439 alias.<\/p>\n<p>  \u0427\u0442\u043e\u0431\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0442\u0435\u0441\u0442, \u043d\u0443\u0436\u043d\u043e \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441, \u0437\u0430\u0442\u0435\u043c \u0443\u0436\u0435 \u2014 \u0442\u0435\u0441\u0442. \u041e\u043d \u0431\u0443\u0434\u0435\u0442 \u0447\u0435\u0441\u0442\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441\u044b HTTP-\u0441\u0435\u0440\u0432\u0435\u0440\u0443, \u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0431\u0443\u0434\u0435\u0442 \u0447\u0435\u0441\u0442\u043d\u043e \u0435\u043c\u0443 \u043e\u0442\u0432\u0435\u0447\u0430\u0442\u044c. \u041d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u0443\u043a\u0430\u0437\u0430\u043b\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 HTTP-\u043f\u043e\u0440\u0442.<\/p>\n<p>  \u0414\u0430\u043b\u0435\u0435 \u0438\u043c\u0435\u0435\u0442 \u0441\u043c\u044b\u0441\u043b \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u043d\u0430 \u0432\u0435\u0441\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f: \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c URL (\u0441 \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u0435\u043c \u0430\u043b\u0438\u0430\u0441\u0430 \u0438 \u0431\u0435\u0437), \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u043f\u043e \u043d\u0435\u043c\u0443 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u0439 \u0440\u0435\u0434\u0438\u0440\u0435\u043a\u0442, \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c, \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u0440\u0435\u0434\u0438\u0440\u0435\u043a\u0442 \u043f\u043e \u043d\u0435\u0439 \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0442\u0435\u0441\u0442 \u043b\u0443\u0447\u0448\u0435 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 \u0442\u0430\u0431\u043b\u0438\u0447\u043d\u044b\u0439, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0442\u0438\u043f\u044b \u043a\u0435\u0439\u0441\u043e\u0432.<\/p>\n<p>  \u042d\u0442\u0430 \u0447\u0430\u0441\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u0438 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043d\u0430 \u0432 <a href=\"https:\/\/youtu.be\/rCJvW2xgnk0?t=7998\">\u0440\u043e\u043b\u0438\u043a\u0435<\/a>. \u041d\u043e, \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u043e, \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e. \u041b\u0438\u0431\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430.<\/p>\n<p>  <a name=\"9\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>\u0414\u0435\u043f\u043b\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h2>\n<p><\/font><br \/>  \u0421\u0435\u0440\u0432\u0438\u0441 \u0433\u043e\u0442\u043e\u0432 \u2014 \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u0435\u0433\u043e \u0441\u043e \u0441\u0432\u043e\u0435\u0433\u043e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0430 \u043d\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440, \u0447\u0442\u043e\u0431\u044b \u043e\u043d \u0431\u044b\u043b \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d 24\/7.<\/p>\n<p>  \u041c\u043d\u0435, \u043a\u0430\u043a \u043b\u044e\u0431\u043e\u043c\u0443 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0443, \u043b\u0435\u043d\u044c \u0434\u0435\u043f\u043b\u043e\u0438\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 \u0440\u0443\u043a\u0430\u043c\u0438, \u0438 \u0445\u043e\u0434\u0438\u0442\u044c \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 \u0432 \u043a\u043e\u0434\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u0435\u043a\u0440\u0430\u0441\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 <a href=\"https:\/\/github.com\/features\/actions\">GitHub Actions<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u043d\u0430 GitHub.<\/p>\n<h3>\u0410\u0440\u0435\u043d\u0434\u0430 \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430<\/h3>\n<p>  \u0414\u0435\u043f\u043b\u043e\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u0443\u0434\u0435\u043c \u043d\u0430 <a href=\"https:\/\/selectel.ru\/services\/cloud\/sharedline\/?utm_source=habr.com&amp;utm_medium=referral&amp;utm_campaign=cloud_article_restapi_130723_content\">\u043e\u0431\u043b\u0430\u0447\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u043b\u0438\u043d\u0435\u0439\u043a\u0438 Shared Line<\/a>. \u0422\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u043e\u043f\u043b\u0430\u0447\u0438\u0432\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0430\u0441\u0442\u044c \u044f\u0434\u0440\u0430 \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, 10, 20 \u0438\u043b\u0438 50%. Shared Line \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0430 \u043e\u0431\u043b\u0430\u043a\u0430 \u0438 \u043d\u0435 \u043f\u0435\u0440\u0435\u043f\u043b\u0430\u0447\u0438\u0432\u0430\u0442\u044c \u0437\u0430 \u043d\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u044b.<\/p>\n<p>  \u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c\u0441\u044f \u0432 \u043f\u0430\u043d\u0435\u043b\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0438 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 <b>\u041e\u0431\u043b\u0430\u0447\u043d\u0430\u044f \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430<\/b>. \u0417\u0430\u0442\u0435\u043c \u2014 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u0435\u0433\u043e.<\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/post_images\/37e\/981\/d1d\/37e981d1d571ed76f1733b7c8b23367a.png\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/37e\/981\/d1d\/37e981d1d571ed76f1733b7c8b23367a.png\"\/><\/div>\n<p>  <i>\u0421\u0435\u0440\u0432\u0438\u0441\u0443 \u043f\u043e\u0434\u043e\u0439\u0434\u0435\u0442 \u041e\u0421 Ubuntu 22.04 LTS, 2 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u044f\u0434\u0440\u0430 \u0441 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0439 \u0433\u0440\u0430\u043d\u0438\u0446\u0435\u0439 \u0432 20% \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438, 2 \u0413\u0411 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438, \u0430 \u0442\u0430\u043a\u0436\u0435 10 \u0413\u0411 \u043d\u0430 \u0441\u0435\u0442\u0435\u0432\u043e\u043c \u0434\u0438\u0441\u043a\u0435 (\u0431\u0430\u0437\u043e\u0432\u044b\u0439 HDD).<\/i><\/p>\n<p>  \u0412 \u043f\u043e\u043b\u0435 Name \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0438\u043c\u044f \u0441\u0435\u0440\u0432\u0435\u0440\u0430. \u0421\u043e\u0432\u0435\u0442\u0443\u044e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u043e\u0441\u043c\u044b\u0441\u043b\u0435\u043d\u043d\u043e\u0435, \u0438\u043d\u0430\u0447\u0435 \u0431\u0443\u0434\u0435\u0442\u0435 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u043f\u0443\u0442\u0430\u0442\u044c\u0441\u044f, \u0435\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0431\u043e\u043b\u044c\u0448\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430.<\/p>\n<p>  \u0417\u0434\u0435\u0441\u044c \u0436\u0435 \u0441\u0440\u0430\u0437\u0443 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c SSH Key \u2014 \u043e\u043d \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0434\u043b\u044f \u0434\u0435\u043f\u043b\u043e\u044f. \u041f\u0430\u0440\u043e\u043b\u044c \u043d\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f, \u043d\u043e \u043c\u043e\u0436\u0435\u0442\u0435 \u0435\u0433\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043d\u0430 \u0432\u0441\u044f\u043a\u0438\u0439 \u0441\u043b\u0443\u0447\u0430\u0439.<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u041a\u043e\u0440\u043e\u0442\u0435\u043d\u044c\u043a\u0438\u0439 \u043b\u0438\u043a\u0431\u0435\u0437 \u043f\u043e SSH-\u043a\u043b\u044e\u0447\u0430\u043c<\/b>                         <\/p>\n<div class=\"spoiler_text\">\u0415\u0441\u043b\u0438 \u0432\u044b \u0432 \u044d\u0442\u043e\u043c \u043d\u0435 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442\u0435\u0441\u044c, \u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0438\u0437\u0443\u0447\u0438\u0442\u044c \u0432\u043e\u043f\u0440\u043e\u0441 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435. \u041d\u043e \u043f\u043e\u043a\u0430 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0437\u043d\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435.<\/p>\n<p>  \u041f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c \u043d\u0430\u043c \u043d\u0430\u0434\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u044e\u0447 \u2014 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e, \u0443 \u0441\u0435\u0431\u044f \u043d\u0430 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0435. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 Mac \u0438\u043b\u0438 Linux, \u0442\u043e \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439 (\u0441 Windows \u044f \u043d\u0435 \u0434\u0440\u0443\u0436\u0443, \u0442\u0443\u0442 \u0432\u0430\u043c \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u043f\u043e\u0433\u0443\u0433\u043b\u0438\u0442\u044c \u0438\u043b\u0438 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0443):<\/p>\n<pre><code class=\"bash\">ssh-keygen -t rsa -b 4096 -C \"some comment\"<\/code><\/pre>\n<p>  \u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u0437\u0430\u0434\u0430\u0441\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u043e\u043f\u0440\u043e\u0441\u043e\u0432, \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0441\u0435 \u0438\u0445 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044f \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f. \u0418\u043c\u0435\u0435\u0442 \u0441\u043c\u044b\u0441\u043b \u0442\u043e\u043b\u044c\u043a\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0438\u043c\u044f \u0444\u0430\u0439\u043b\u0430 \u043a\u043b\u044e\u0447\u0430 \u0438 \u043f\u0443\u0442\u044c \u0434\u043e \u043d\u0435\u0433\u043e.<\/p>\n<p>  \u0412 \u0438\u0442\u043e\u0433\u0435, \u0432\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0435 \u0434\u0432\u0430 \u0444\u0430\u0439\u043b\u0430:<\/p>\n<ul>\n<li> <code>[path]\/[key_name] <\/code> \u2014 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0439 \u043a\u043b\u044e\u0447, <\/li>\n<li> <code>[path]\/[key_name].pub<\/code> \u2014 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u043a\u043b\u044e\u0447. <\/li>\n<\/ul>\n<p>  \u0421\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e \u043a\u043b\u044e\u0447\u0430 \u043c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0434\u0438\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0443. \u042d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u0438 \u0435\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 (\u0441\u043c. \u0441\u043a\u0440\u0438\u043d\u0448\u043e\u0442\u044b \u0432\u044b\u0448\u0435). \u0421\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u043e\u0433\u043e \u043a\u043b\u044e\u0447\u0430 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f GitHub Actions, \u0442\u0443\u0434\u0430 \u043c\u044b \u0435\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0447\u0443\u0442\u044c \u043f\u043e\u0437\u0436\u0435.  <\/div>\n<\/p><\/div>\n<h3>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 GitHub Workflow<\/h3>\n<p>  \u0423 GitHub \u0435\u0441\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441 Actions, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 Workflow \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u0435\u043f\u043b\u043e\u0439 \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u044b, \u043f\u0440\u043e\u0433\u043e\u043d \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u043c\u043d\u043e\u0433\u043e\u0435 \u0434\u0440\u0443\u0433\u043e\u0435. \u0420\u0430\u0437\u0431\u0435\u0440\u0435\u043c\u0441\u044f, \u043a\u0430\u043a \u0435\u0433\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c.<\/p>\n<p>  \u0414\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f Workflow \u043a \u0441\u0432\u043e\u0435\u043c\u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0443 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c yaml \u0444\u0430\u0439\u043b \u0441 \u0435\u0433\u043e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0435\u0439 \u0432 \u043f\u0430\u043f\u043a\u0443: .github\/workflows \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u041d\u0430\u0437\u043e\u0432\u0435\u043c \u043d\u0430\u0448 \u0444\u0430\u0439\u043b deploy.yaml.<\/p>\n<p>  \u0421\u0430\u043c\u043e\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0435 \u2014 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c yaml-\u0444\u0430\u0439\u043b \u0441 \u0435\u0433\u043e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0435\u0439 \u0432 \u043f\u0430\u043f\u043a\u0443 .github\/workflows, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u041d\u0430\u0437\u043e\u0432\u0435\u043c \u043d\u0430\u0448 \u0444\u0430\u0439\u043b deploy.yaml. \u041e\u043d \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u0437 \u0442\u0440\u0435\u0445 \u043e\u0431\u0449\u0438\u0445 \u0441\u0435\u043a\u0446\u0438\u0439:<\/p>\n<ul>\n<li>name \u2014 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 workflow, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 Actions,<\/li>\n<li>on \u2014 \u0443\u0441\u043b\u043e\u0432\u0438\u044f, \u043f\u0440\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c\u0441\u044f workflow,<\/li>\n<li>jobs \u2014 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0440\u043e\u0434\u0435\u043b\u0430\u0442\u044c.<\/li>\n<\/ul>\n<p>  \u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u043f\u0435\u0440\u0432\u044b\u0445 \u0434\u0432\u0443\u0445, \u043e\u043d\u0438 \u0441\u0430\u043c\u044b\u0435 \u043f\u0440\u043e\u0441\u0442\u044b\u0435:<\/p>\n<pre><code class=\"plaintext\"># .github\/workflows\/deploy.yaml  name: Deploy App # \u0414\u0430\u0435\u043c \u043e\u0441\u043c\u044b\u0441\u043b\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f  on:   workflow_dispatch: # \u0420\u0443\u0447\u043a\u043e\u0439 \u0437\u0430\u043f\u0443\u0441\u043a     inputs: # \u0427\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0432\u0432\u0435\u0441\u0442\u0438 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435       tag: # \u041c\u044b \u0431\u0443\u0434\u0435\u043c \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0442\u0435\u0433 \u0434\u043b\u044f \u0434\u0435\u043f\u043b\u043e\u044f         description: 'Tag to deploy'         required: true<\/code><\/pre>\n<p>  <\/p>\n<blockquote><p><i>\u0412 \u0442\u0430\u043a\u043e\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 workflow \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0440\u0443\u0447\u043d\u0443\u044e. \u041f\u0440\u0438\u0442\u043e\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0443\u043a\u0430\u0437\u0430\u0442\u044c git-\u0442\u0435\u0433, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u0431\u0443\u0434\u0435\u043c \u0434\u0435\u043f\u043b\u043e\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441. \u041c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0430\u043c\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0449\u0435 \u2014 \u0434\u0435\u043f\u043b\u043e\u0438\u0442\u044c \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u043f\u0443\u0448\u0435\/\u043c\u0435\u0440\u0436\u0435 \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u0432\u0435\u0442\u043a\u0443, \u043d\u043e \u043c\u043d\u0435 \u0442\u0430\u043a\u043e\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f. \u0425\u043e\u0447\u0443 \u0441\u0430\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e \u0434\u0435\u043b\u043e.<\/i><\/p><\/blockquote>\n<p>  \u0414\u0430\u043b\u0435\u0435 \u0438\u0434\u0435\u0442 \u0441\u0435\u043a\u0446\u0438\u044f jobs. \u041e\u043d\u0430 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0434\u0432\u0443\u0445 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0445 \u0441\u0435\u043a\u0446\u0438\u0439 \u2014 deploy \u0438 steps. \u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e \u2014 deploy:<\/p>\n<pre><code class=\"plaintext\"># .github\/workflows\/deploy.yaml  # name: ..., on: ...  jobs:   deploy:     runs-on: ubuntu-latest # \u041e\u0421 \u0434\u043b\u044f runner     env: # \u0412\u0432\u043e\u0434\u0438\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043b\u0435\u0435       HOST: root@&lt;your_ip> # \u043b\u043e\u0433\u0438\u043d \/ \u0445\u043e\u0441\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u0435\u043f\u043b\u043e\u0438\u043c       DEPLOY_DIRECTORY: \/root\/apps\/url-shortener # \u043f\u0430\u043f\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435       CONFIG_PATH: \/root\/apps\/url-shortener\/config\/prod.yaml # \u043a\u043e\u043d\u0444\u0438\u0433 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435       ENV_FILE_PATH: \/root\/apps\/url-shortener\/config.env # env-\u0444\u0430\u0439\u043b \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435<\/code><\/pre>\n<p>  \u0414\u0430\u043b\u0435\u0435 \u0438\u0434\u0435\u0442 \u0441\u0435\u043a\u0446\u0438\u044f steps \u2014 \u043e\u043d\u0430 \u0441\u0430\u043c\u0430\u044f \u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0438 \u0437\u0430\u0431\u043e\u0440\u0438\u0441\u0442\u0430\u044f:<\/p>\n<pre><code class=\"plaintext\"># .github\/workflows\/deploy.yaml  # name: ..., on: ...  jobs:     # deploy: ...          steps:       - name: Checkout repository         uses: actions\/checkout@v2         with:           ref: ${{ github.event.inputs.tag }}       - name: Check if tag exists         run: |           git fetch --all --tags           if ! git tag | grep -q \"^${{ github.event.inputs.tag }}$\"; then             echo \"error: Tag '${{ github.event.inputs.tag }}' not found\"             exit 1           fi       - name: Set up Go         uses: actions\/setup-go@v2         with:           go-version: 1.20.2       - name: Build app         run: |           go mod download           go build -o url-shortener .\/cmd\/url-shortener       - name: Deploy to VM         run: |           sudo apt-get install -y ssh rsync           echo \"$DEPLOY_SSH_KEY\" > deploy_key.pem           chmod 600 deploy_key.pem           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"mkdir -p ${{ env.DEPLOY_DIRECTORY }}\"           rsync -avz -e 'ssh -i deploy_key.pem -o StrictHostKeyChecking=no' --exclude='.git' .\/ ${{ env.HOST }}:${{ env.DEPLOY_DIRECTORY }}         env:           DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}       - name: Remove old systemd service file         run: |           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"rm -f \/etc\/systemd\/system\/url-shortener.service\"       - name: List workspace contents         run: |           echo \"Listing deployment folder contents:\"           ls -la ${{ github.workspace }}\/deployment       - name: Create environment file on server         run: |           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"touch ${{ env.ENV_FILE_PATH }}\"           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"chmod 600 ${{ env.ENV_FILE_PATH }}\"           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"echo 'CONFIG_PATH=${{ env.CONFIG_PATH }}' > ${{ env.ENV_FILE_PATH }}\"           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"echo 'HTTP_SERVER_PASSWORD=${{ secrets.AUTH_PASS }}' >> ${{ env.ENV_FILE_PATH }}\"       - name: Copy systemd service file         run: |           scp -i deploy_key.pem -o StrictHostKeyChecking=no ${{ github.workspace }}\/deployment\/url-shortener.service ${{ env.HOST }}:\/tmp\/url-shortener.service           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"mv \/tmp\/url-shortener.service \/etc\/systemd\/system\/url-shortener.service\"       - name: Start application         run: |           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"systemctl daemon-reload &amp;&amp; systemctl restart url-shortener.service\"<\/code><\/pre>\n<p>  \u0412 steps \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f \u043f\u043e \u043f\u043e\u0440\u044f\u0434\u043a\u0443. \u041a\u0430\u0436\u0434\u044b\u0439 \u0448\u0430\u0433 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 (\u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b):<\/p>\n<ul>\n<li>name \u2014 \u0438\u043c\u044f \u0448\u0430\u0433\u0430, \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f workflow, \u043f\u0438\u0448\u0435\u043c \u0447\u0442\u043e-\u0442\u043e \u043e\u0441\u043c\u044b\u0441\u043b\u0435\u043d\u043d\u043e\u0435.<\/li>\n<li>uses \u2014 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0432\u043d\u0435\u0448\u043d\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u044b. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, uses: actions\/setup-go@v2 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u0447\u0442\u043e \u0448\u0430\u0433 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 setup-go, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0435 \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 actions \u043d\u0430 GitHub.<\/li>\n<li>with \u2014 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u044e\u0442\u0441\u044f \u0432 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435.<\/li>\n<li>env \u2014 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0448\u0430\u0433\u0430.<\/li>\n<li>run \u2014 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u0430\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0430.<\/li>\n<\/ul>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u043a\u0430\u0436\u0434\u044b\u0439 \u0448\u0430\u0433:<\/p>\n<ul>\n<li>Checkout repository: \u043a\u043b\u043e\u043d\u0438\u0440\u0443\u0435\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0432 runner.<\/li>\n<li>Check if tag exists: \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u043b\u0438 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u0442\u0435\u0433.<\/li>\n<li>Set up Go: \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e Go.<\/li>\n<li>Build app: \u0421\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0438 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435.<\/li>\n<li>Deploy to VM: \u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0444\u0430\u0439\u043b\u044b \u0438\u0437 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u043d\u0430 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u0443\u044e \u043c\u0430\u0448\u0438\u043d\u0443.<\/li>\n<li>Remove old systemd service file: \u0423\u0434\u0430\u043b\u044f\u0435\u043c \u0441\u0442\u0430\u0440\u044b\u0439 \u0444\u0430\u0439\u043b \u0441\u0435\u0440\u0432\u0438\u0441\u0430 systemd \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435.<\/li>\n<li>List workspace contents: \u0412\u044b\u0432\u043e\u0434\u0438\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0440\u0430\u0431\u043e\u0447\u0435\u0433\u043e \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430 \u043d\u0430 runner.<\/li>\n<li>Create environment file on server: \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0430\u0439\u043b \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435.<\/li>\n<li>Copy systemd service file: \u041a\u043e\u043f\u0438\u0440\u0443\u0435\u043c \u0444\u0430\u0439\u043b \u0441\u0435\u0440\u0432\u0438\u0441\u0430 systemd \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440.<\/li>\n<li>Start application: \u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435.<\/li>\n<\/ul>\n<p>  \u041a\u0430\u043a \u0432\u0438\u0434\u0438\u0442\u0435, \u0437\u0434\u0435\u0441\u044c \u043c\u044b \u043f\u0440\u043e\u0434\u0435\u043b\u044b\u0432\u0430\u0435\u043c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u044f\u0446\u0438\u0438 \u0441 \u0444\u0430\u0439\u043b\u043e\u043c \u043a\u043e\u043d\u0444\u0438\u0433\u0430 systemd, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u044d\u0442\u0443 \u0441\u043b\u0443\u0436\u0431\u0443. \u042d\u0442\u043e \u043d\u0430\u0434\u0435\u0436\u043d\u0435\u0439, \u0447\u0435\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0435\u0433\u043e \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e. \u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u0435\u0441\u043b\u0438 \u0441\u0435\u0440\u0432\u0438\u0441 \u0443\u043f\u0430\u0434\u0435\u0442, systemd \u0435\u0433\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442.<\/p>\n<p>  \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u044d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b \u0432\u043d\u0443\u0442\u0440\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u2014 deployment\/url-shortener.service. \u0421\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0444\u0430\u0439\u043b\u0430:<\/p>\n<pre><code class=\"plaintext\">[Unit] Description=Url Shortener After=network.target  [Service] User=root WorkingDirectory=\/root\/apps\/url-shortener ExecStart=\/root\/apps\/url-shortener\/url-shortener Restart=always RestartSec=4 StandardOutput=inherit EnvironmentFile=\/root\/apps\/url-shortener\/config.env  [Install] WantedBy=multi-user.target<\/code><\/pre>\n<p>  \u0422\u0430\u043a\u0436\u0435 \u0432 workflow \u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u0435\u0442 \u0444\u0430\u0439\u043b prod.yaml. \u042d\u0442\u043e \u043a\u043e\u043d\u0444\u0438\u0433, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435, \u043e\u043d \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e.<\/p>\n<pre><code class=\"plaintext\"># config\/prod.yaml  env: \"prod\" storage_path: \".\/storage.db\" http_server:   address: \"0.0.0.0:8082\" # 0.0.0.0 \u0432\u043c\u0435\u0441\u0442\u043e localhost, \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438 \u0432\u043d\u0435\u0448\u043d\u0438\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b   timeout: 4s   idle_timeout: 30s   user: \"some_username\" # \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0442\u043e\u043b\u044c\u043a\u043e user, \u043d\u043e \u043d\u0435 password. \u041e \u043f\u0430\u0440\u043e\u043b\u0435 \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c \u043d\u0438\u0436\u0435<\/code><\/pre>\n<p>  \u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u043c\u043e\u0436\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 \u043d\u0430 GitHub, \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u0441\u0435\u043a\u0440\u0435\u0442\u044b SSH-\u043a\u043b\u044e\u0447 (DEPLOY_SSH_KEY) \u0438 \u043f\u0430\u0440\u043e\u043b\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0430 (AUTH_PASS):<\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/post_images\/2db\/7ce\/e76\/2db7cee7651e07a3a5ebb0dfc0632235.png\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/2db\/7ce\/e76\/2db7cee7651e07a3a5ebb0dfc0632235.png\"\/><\/div>\n<p>  <i>GitHub, Settings\/Secrets and variables\/Actions.<\/i><\/p>\n<blockquote><p><i>GitHub, Settings \u2192 Secrets and variables \u2192Actions<\/i>. \u0418\u043c\u0435\u043d\u0430 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c, \u0442\u0430\u043a \u043a\u0430\u043a \u043e\u043d\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0432 \u043d\u0430\u0448\u0435\u043c \u0444\u0430\u0439\u043b\u0435 workflow.<\/p><\/blockquote>\n<p>  <\/p>\n<h4>\u0414\u0435\u043f\u043b\u043e\u0439 \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d<\/h4>\n<p>  \u0415\u0441\u043b\u0438 \u0432\u044b \u0432\u0441\u0435 \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e, \u0442\u043e \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043b\u0438\u0448\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0442\u0435\u0433 \u0438 \u043d\u0430\u0436\u0430\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443 \u0434\u0435\u043f\u043b\u043e\u044f.<\/p>\n<p>  \u0422\u0435\u0433 \u044f \u043e\u0431\u044b\u0447\u043d\u043e \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u044e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e:<\/p>\n<pre><code class=\"bash\">git tag v0.0.1 &amp;&amp; git push origin v0.0.1<\/code><\/pre>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043d\u0430 GitHub \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0441\u0435\u043a\u0446\u0438\u044e Actions \u0438 \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 Workflow \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u0441\u0432\u043e\u0439 Deploy App:<\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/post_images\/4f3\/76c\/4eb\/4f376c4ebbab05f19f5bc89d64f941dd.png\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/4f3\/76c\/4eb\/4f376c4ebbab05f19f5bc89d64f941dd.png\"\/><\/div>\n<p>  \u041d\u0430\u0436\u0438\u043c\u0430\u0435\u043c Run workflow \u0438 \u0436\u0434\u0435\u043c. \u0415\u0441\u043b\u0438 \u0432\u0441\u0435 \u0441\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u043e, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0438\u0441\u0443 \u043f\u043e \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u043c\u0443 IP: <code>http:\/\/[your_ip]:[http-port]\/url<\/code>.<\/p>\n<p>  \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u043f\u043e\u0440\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043d\u0435 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 80, \u0430 \u0442\u043e\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043a\u0430\u0437\u0430\u043d \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u0430.<\/p>\n<p>  <a name=\"12\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p><\/font><br \/>  \u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 REST API-\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u2014 \u043e\u0431\u0448\u0438\u0440\u043d\u0430\u044f \u0442\u0435\u043c\u0430, \u0438 \u0432 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0441\u043b\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043e\u0431\u044a\u044f\u0441\u043d\u0438\u0442\u044c \u0432\u0441\u0435 \u0430\u0441\u043f\u0435\u043a\u0442\u044b. \u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u043d\u0440\u0430\u0432\u044f\u0442\u0441\u044f \u0442\u0430\u043a\u0438\u0435 \u0442\u0435\u043c\u044b, \u0438 \u0432 \u0446\u0435\u043b\u043e\u043c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043d\u0430 Go, \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0439\u0442\u0435\u0441\u044c \u043d\u0430 \u043c\u043e\u0439 \u043a\u0430\u043d\u0430\u043b. \u041d\u0430\u043f\u043e\u043c\u043d\u044e, \u0447\u0442\u043e \u0432\u0438\u0434\u0435\u043e\u0432\u0435\u0440\u0441\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u043f\u043e <a href=\"https:\/\/www.youtube.com\/watch?v=rCJvW2xgnk0\">\u0441\u0441\u044b\u043b\u043a\u0435<\/a>.<\/p>\n<p>  <i>\u0423 \u043c\u0435\u043d\u044f \u043c\u043d\u043e\u0433\u043e \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0435\u0439, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u043e\u0439 \u043d\u0430 Go \u0438 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e. \u041f\u0440\u043e\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u0437\u0430 \u043c\u043e\u0435\u0439 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u043b\u0435\u0434\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 <a href=\"https:\/\/t.me\/ntuzov\">\u043c\u043e\u0439 Telegram-\u043a\u0430\u043d\u0430\u043b<\/a>. \u0410\u043d\u043e\u043d\u0441\u044b \u0432\u0441\u0435\u0445 \u0441\u0442\u0430\u0442\u044c\u0435\u0439, \u0440\u043e\u043b\u0438\u043a\u043e\u0432, \u043f\u043e\u0434\u043a\u0430\u0441\u0442\u043e\u0432 \u0438 \u043f\u0440\u043e\u0447\u0435\u0433\u043e \u0431\u0443\u0434\u0443\u0442 \u0442\u0430\u043c. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u044f \u043f\u0438\u0448\u0443 \u0442\u0430\u043c \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0435 \u0433\u0430\u0439\u0434\u044b \u0432 \u0431\u043e\u043b\u0435\u0435 \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435.<\/i><\/div>\n<\/div>\n<\/div>\n<p> <!----> <!----><\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/companies\/selectel\/articles\/747738\/\"> https:\/\/habr.com\/ru\/companies\/selectel\/articles\/747738\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/vj\/eb\/gn\/vjebgnmqximd2g6hmph8yit4-ee.png\" data-src=\"https:\/\/habrastorage.org\/webt\/vj\/eb\/gn\/vjebgnmqximd2g6hmph8yit4-ee.png\"\/><\/div>\n<p>  \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 REST API \u0441\u0435\u0440\u0432\u0438\u0441 \u2014 URL Shortener \u2014\u00a0\u0438 \u0437\u0430\u0434\u0435\u043f\u043b\u043e\u0438\u043c \u0435\u0433\u043e \u043d\u0430 <a href=\"https:\/\/selectel.ru\/services\/cloud\/servers\/?utm_source=habr.com&amp;utm_medium=referral&amp;utm_campaign=cloud_article_restapi_130723_content\">\u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440<\/a> \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e GitHub Actions.<\/p>\n<p>  \u0413\u043e\u0432\u043e\u0440\u044f \u00ab\u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439\u00bb, \u044f \u0438\u043c\u0435\u044e \u0432 \u0432\u0438\u0434\u0443, \u0447\u0442\u043e \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043d\u0435 \u0438\u0433\u0440\u0443\u0448\u0435\u0447\u043d\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442, \u0430 \u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044e:<\/p>\n<ul>\n<li>\u043c\u044b \u0432\u044b\u0431\u0435\u0440\u0435\u043c \u0434\u043b\u044f \u043d\u0435\u0433\u043e \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 http-\u0440\u043e\u0443\u0442\u0435\u0440,<\/li>\n<li>\u043f\u043e\u0437\u0430\u0431\u043e\u0442\u0438\u043c\u0441\u044f \u043e \u043b\u043e\u0433\u0430\u0445,<\/li>\n<li>\u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0442\u0435\u0441\u0442\u044b: unit-\u0442\u0435\u0441\u0442\u044b, \u0442\u0435\u0441\u0442\u044b \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u043e\u0432 \u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435,<\/li>\n<li>\u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0434\u0435\u043f\u043b\u043e\u0439 \u0447\u0435\u0440\u0435\u0437 GitHub Actions \u0438 \u0434\u0440.<\/li>\n<\/ul>\n<p>  \u041d\u043e \u0432\u0430\u0436\u043d\u043e \u043f\u043e\u043d\u0438\u043c\u0430\u0442\u044c, \u0447\u0442\u043e \u00ab\u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u043a \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0443\u00bb != \u00ab\u044d\u043d\u0442\u0435\u0440\u043f\u0440\u0430\u0439\u0437\u00bb.<\/p>\n<p>  \u041a\u0440\u0430\u0442\u043a\u043e \u043e\u0431\u043e \u043c\u043d\u0435: \u043c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 <a href=\"https:\/\/t.me\/ntuzov\">\u041d\u0438\u043a\u043e\u043b\u0430\u0439<\/a>, \u044f \u043c\u043d\u043e\u0433\u043e \u043b\u0435\u0442 \u0437\u0430\u043d\u0438\u043c\u0430\u044e\u0441\u044c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u043e\u0439 \u043d\u0430 Go, \u043e\u0447\u0435\u043d\u044c \u043b\u044e\u0431\u043b\u044e \u044d\u0442\u043e\u0442 \u044f\u0437\u044b\u043a. \u0422\u0430\u043a\u0436\u0435 \u0432\u0435\u0434\u0443 \u0441\u0432\u043e\u0439 <a href=\"https:\/\/www.youtube.com\/@nikolay_tuzov\/\">YouTube-\u043a\u0430\u043d\u0430\u043b<\/a>, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0435\u0441\u0442\u044c <a href=\"https:\/\/www.youtube.com\/watch?v=rCJvW2xgnk0\">\u0432\u0438\u0434\u0435\u043e\u0432\u0435\u0440\u0441\u0438\u044f<\/a> \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0433\u0430\u0439\u0434\u0430, \u0441 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u044b\u043c\u0438 \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u044f\u043c\u0438.  <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-350225","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/350225","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=350225"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/350225\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=350225"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=350225"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=350225"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}