[{"data":1,"prerenderedAt":3302},["ShallowReactive",2],{"content:\u002F07-rpc-grpc\u002F03-grpc-go-server-client":3},{"title":4,"description":5,"path":6,"body":7},"gRPC в Go: сервер, клиент и codegen","В gRPC разработчик обычно не пишет HTTP handler руками. Он описывает контракт в .proto, генерирует Go-код, реализует server interface и использует generated client.","\u002F07-rpc-grpc\u002F03-grpc-go-server-client",{"type":8,"value":9,"toc":3283},"minimark",[10,14,23,26,36,39,44,47,69,72,105,108,148,151,157,162,172,192,199,287,292,381,384,413,432,434,438,594,605,608,708,722,724,728,731,737,740,772,779,781,785,1413,1422,1429,1446,1450,1461,1783,1791,1793,1797,2140,2146,2153,2167,2169,2176,2179,2194,2197,2211,2214,2589,2602,2604,2608,2611,2696,2707,2709,2713,2719,2747,2754,2756,2760,2804,2806,2810,2841,2843,2847,2884,2886,2890,2932,3092,3279],[11,12,4],"h1",{"id":13},"grpc-в-go-сервер-клиент-и-codegen",[15,16,17,18,22],"p",{},"В gRPC разработчик обычно не пишет HTTP handler руками. Он описывает контракт в ",[19,20,21],"code",{},".proto",", генерирует Go-код, реализует server interface и использует generated client.",[15,24,25],{},"Цепочка такая:",[27,28,34],"pre",{"className":29,"code":31,"language":32,"meta":33},[30],"language-text","calculator.proto\n   ↓ protoc + plugins\ncalculator.pb.go\ncalculator_grpc.pb.go\n   ↓\nваш server implementation\nваш client code\n","text","",[19,35,31],{"__ignoreMap":33},[37,38],"hr",{},[40,41,43],"h2",{"id":42},"инструменты","Инструменты",[15,45,46],{},"Нужны три вещи:",[48,49,50,57,63],"ul",{},[51,52,53,56],"li",{},[19,54,55],{},"protoc"," - компилятор protobuf;",[51,58,59,62],{},[19,60,61],{},"protoc-gen-go"," - генератор Go-типов для messages;",[51,64,65,68],{},[19,66,67],{},"protoc-gen-go-grpc"," - генератор gRPC client\u002Fserver кода.",[15,70,71],{},"Установка Go-плагинов:",[27,73,77],{"className":74,"code":75,"language":76,"meta":33,"style":33},"language-bash shiki shiki-themes github-dark","go install google.golang.org\u002Fprotobuf\u002Fcmd\u002Fprotoc-gen-go@latest\ngo install google.golang.org\u002Fgrpc\u002Fcmd\u002Fprotoc-gen-go-grpc@latest\n","bash",[19,78,79,95],{"__ignoreMap":33},[80,81,84,88,92],"span",{"class":82,"line":83},"line",1,[80,85,87],{"class":86},"svObZ","go",[80,89,91],{"class":90},"sU2Wk"," install",[80,93,94],{"class":90}," google.golang.org\u002Fprotobuf\u002Fcmd\u002Fprotoc-gen-go@latest\n",[80,96,98,100,102],{"class":82,"line":97},2,[80,99,87],{"class":86},[80,101,91],{"class":90},[80,103,104],{"class":90}," google.golang.org\u002Fgrpc\u002Fcmd\u002Fprotoc-gen-go-grpc@latest\n",[15,106,107],{},"Генерация:",[27,109,111],{"className":74,"code":110,"language":76,"meta":33,"style":33},"protoc \\\n  --go_out=. --go_opt=paths=source_relative \\\n  --go-grpc_out=. --go-grpc_opt=paths=source_relative \\\n  proto\u002Fcalculator\u002Fv1\u002Fcalculator.proto\n",[19,112,113,121,131,142],{"__ignoreMap":33},[80,114,115,117],{"class":82,"line":83},[80,116,55],{"class":86},[80,118,120],{"class":119},"sDLfK"," \\\n",[80,122,123,126,129],{"class":82,"line":97},[80,124,125],{"class":119},"  --go_out=.",[80,127,128],{"class":119}," --go_opt=paths=source_relative",[80,130,120],{"class":119},[80,132,134,137,140],{"class":82,"line":133},3,[80,135,136],{"class":119},"  --go-grpc_out=.",[80,138,139],{"class":119}," --go-grpc_opt=paths=source_relative",[80,141,120],{"class":119},[80,143,145],{"class":82,"line":144},4,[80,146,147],{"class":90},"  proto\u002Fcalculator\u002Fv1\u002Fcalculator.proto\n",[15,149,150],{},"После этого обычно появляются:",[27,152,155],{"className":153,"code":154,"language":32,"meta":33},[30],"proto\u002Fcalculator\u002Fv1\u002Fcalculator.pb.go\nproto\u002Fcalculator\u002Fv1\u002Fcalculator_grpc.pb.go\n",[19,156,154],{"__ignoreMap":33},[158,159,161],"h3",{"id":160},"вариант-с-buf","Вариант с Buf",[15,163,164,165,167,168,171],{},"В современных проектах вместо длинной команды ",[19,166,55],{}," часто используют ",[19,169,170],{},"buf",". Он не заменяет protobuf и gRPC, а даёт удобный workflow вокруг схем:",[48,173,174,177,183,186,189],{},[51,175,176],{},"единая конфигурация генерации;",[51,178,179,180,182],{},"lint ",[19,181,21],{}," файлов;",[51,184,185],{},"breaking change checks;",[51,187,188],{},"воспроизводимый codegen в CI;",[51,190,191],{},"меньше ручных shell-команд у каждого разработчика.",[15,193,194,195,198],{},"Минимальный ",[19,196,197],{},"buf.yaml",":",[27,200,204],{"className":201,"code":202,"language":203,"meta":33,"style":33},"language-yaml shiki shiki-themes github-dark","version: v2\nmodules:\n  - path: proto\nlint:\n  use:\n    - STANDARD\nbreaking:\n  use:\n    - FILE\n","yaml",[19,205,206,219,227,240,247,255,264,272,279],{"__ignoreMap":33},[80,207,208,212,216],{"class":82,"line":83},[80,209,211],{"class":210},"s4JwU","version",[80,213,215],{"class":214},"s95oV",": ",[80,217,218],{"class":90},"v2\n",[80,220,221,224],{"class":82,"line":97},[80,222,223],{"class":210},"modules",[80,225,226],{"class":214},":\n",[80,228,229,232,235,237],{"class":82,"line":133},[80,230,231],{"class":214},"  - ",[80,233,234],{"class":210},"path",[80,236,215],{"class":214},[80,238,239],{"class":90},"proto\n",[80,241,242,245],{"class":82,"line":144},[80,243,244],{"class":210},"lint",[80,246,226],{"class":214},[80,248,250,253],{"class":82,"line":249},5,[80,251,252],{"class":210},"  use",[80,254,226],{"class":214},[80,256,258,261],{"class":82,"line":257},6,[80,259,260],{"class":214},"    - ",[80,262,263],{"class":90},"STANDARD\n",[80,265,267,270],{"class":82,"line":266},7,[80,268,269],{"class":210},"breaking",[80,271,226],{"class":214},[80,273,275,277],{"class":82,"line":274},8,[80,276,252],{"class":210},[80,278,226],{"class":214},[80,280,282,284],{"class":82,"line":281},9,[80,283,260],{"class":214},[80,285,286],{"class":90},"FILE\n",[15,288,194,289,198],{},[19,290,291],{},"buf.gen.yaml",[27,293,295],{"className":201,"code":294,"language":203,"meta":33,"style":33},"version: v2\nplugins:\n  - remote: buf.build\u002Fprotocolbuffers\u002Fgo\n    out: .\n    opt:\n      - paths=source_relative\n  - remote: buf.build\u002Fgrpc\u002Fgo\n    out: .\n    opt:\n      - paths=source_relative\n",[19,296,297,305,312,324,334,341,349,360,368,374],{"__ignoreMap":33},[80,298,299,301,303],{"class":82,"line":83},[80,300,211],{"class":210},[80,302,215],{"class":214},[80,304,218],{"class":90},[80,306,307,310],{"class":82,"line":97},[80,308,309],{"class":210},"plugins",[80,311,226],{"class":214},[80,313,314,316,319,321],{"class":82,"line":133},[80,315,231],{"class":214},[80,317,318],{"class":210},"remote",[80,320,215],{"class":214},[80,322,323],{"class":90},"buf.build\u002Fprotocolbuffers\u002Fgo\n",[80,325,326,329,331],{"class":82,"line":144},[80,327,328],{"class":210},"    out",[80,330,215],{"class":214},[80,332,333],{"class":119},".\n",[80,335,336,339],{"class":82,"line":249},[80,337,338],{"class":210},"    opt",[80,340,226],{"class":214},[80,342,343,346],{"class":82,"line":257},[80,344,345],{"class":214},"      - ",[80,347,348],{"class":90},"paths=source_relative\n",[80,350,351,353,355,357],{"class":82,"line":266},[80,352,231],{"class":214},[80,354,318],{"class":210},[80,356,215],{"class":214},[80,358,359],{"class":90},"buf.build\u002Fgrpc\u002Fgo\n",[80,361,362,364,366],{"class":82,"line":274},[80,363,328],{"class":210},[80,365,215],{"class":214},[80,367,333],{"class":119},[80,369,370,372],{"class":82,"line":281},[80,371,338],{"class":210},[80,373,226],{"class":214},[80,375,377,379],{"class":82,"line":376},10,[80,378,345],{"class":214},[80,380,348],{"class":90},[15,382,383],{},"Тогда обычный цикл разработки выглядит так:",[27,385,387],{"className":74,"code":386,"language":76,"meta":33,"style":33},"buf lint\nbuf generate\ngo test .\u002F...\n",[19,388,389,396,403],{"__ignoreMap":33},[80,390,391,393],{"class":82,"line":83},[80,392,170],{"class":86},[80,394,395],{"class":90}," lint\n",[80,397,398,400],{"class":82,"line":97},[80,399,170],{"class":86},[80,401,402],{"class":90}," generate\n",[80,404,405,407,410],{"class":82,"line":133},[80,406,87],{"class":86},[80,408,409],{"class":90}," test",[80,411,412],{"class":90}," .\u002F...\n",[15,414,415,416,418,419,421,422,425,426,428,429,431],{},"Если проект не использует remote plugins, можно поставить ",[19,417,61],{}," и ",[19,420,67],{}," локально и указать ",[19,423,424],{},"local"," plugins в ",[19,427,291],{},". Смысл тот же: ",[19,430,21],{}," меняется руками, generated Go-код обновляется командой, а не редактируется вручную.",[37,433],{},[40,435,437],{"id":436},"контракт","Контракт",[27,439,443],{"className":440,"code":441,"language":442,"meta":33,"style":33},"language-proto shiki shiki-themes github-dark","syntax = \"proto3\";\n\npackage calculator.v1;\n\noption go_package = \"example.com\u002Fgrpc-demo\u002Fproto\u002Fcalculator\u002Fv1;calculatorv1\";\n\nservice CalculatorService {\n  rpc Add(AddRequest) returns (AddResponse);\n  rpc Divide(DivideRequest) returns (DivideResponse);\n}\n\nmessage AddRequest {\n  int64 a = 1;\n  int64 b = 2;\n}\n\nmessage AddResponse {\n  int64 result = 1;\n}\n\nmessage DivideRequest {\n  int64 dividend = 1;\n  int64 divisor = 2;\n}\n\nmessage DivideResponse {\n  double result = 1;\n}\n","proto",[19,444,445,450,456,461,465,470,474,479,484,489,494,499,505,511,517,522,527,533,539,544,549,555,561,567,572,577,583,589],{"__ignoreMap":33},[80,446,447],{"class":82,"line":83},[80,448,449],{},"syntax = \"proto3\";\n",[80,451,452],{"class":82,"line":97},[80,453,455],{"emptyLinePlaceholder":454},true,"\n",[80,457,458],{"class":82,"line":133},[80,459,460],{},"package calculator.v1;\n",[80,462,463],{"class":82,"line":144},[80,464,455],{"emptyLinePlaceholder":454},[80,466,467],{"class":82,"line":249},[80,468,469],{},"option go_package = \"example.com\u002Fgrpc-demo\u002Fproto\u002Fcalculator\u002Fv1;calculatorv1\";\n",[80,471,472],{"class":82,"line":257},[80,473,455],{"emptyLinePlaceholder":454},[80,475,476],{"class":82,"line":266},[80,477,478],{},"service CalculatorService {\n",[80,480,481],{"class":82,"line":274},[80,482,483],{},"  rpc Add(AddRequest) returns (AddResponse);\n",[80,485,486],{"class":82,"line":281},[80,487,488],{},"  rpc Divide(DivideRequest) returns (DivideResponse);\n",[80,490,491],{"class":82,"line":376},[80,492,493],{},"}\n",[80,495,497],{"class":82,"line":496},11,[80,498,455],{"emptyLinePlaceholder":454},[80,500,502],{"class":82,"line":501},12,[80,503,504],{},"message AddRequest {\n",[80,506,508],{"class":82,"line":507},13,[80,509,510],{},"  int64 a = 1;\n",[80,512,514],{"class":82,"line":513},14,[80,515,516],{},"  int64 b = 2;\n",[80,518,520],{"class":82,"line":519},15,[80,521,493],{},[80,523,525],{"class":82,"line":524},16,[80,526,455],{"emptyLinePlaceholder":454},[80,528,530],{"class":82,"line":529},17,[80,531,532],{},"message AddResponse {\n",[80,534,536],{"class":82,"line":535},18,[80,537,538],{},"  int64 result = 1;\n",[80,540,542],{"class":82,"line":541},19,[80,543,493],{},[80,545,547],{"class":82,"line":546},20,[80,548,455],{"emptyLinePlaceholder":454},[80,550,552],{"class":82,"line":551},21,[80,553,554],{},"message DivideRequest {\n",[80,556,558],{"class":82,"line":557},22,[80,559,560],{},"  int64 dividend = 1;\n",[80,562,564],{"class":82,"line":563},23,[80,565,566],{},"  int64 divisor = 2;\n",[80,568,570],{"class":82,"line":569},24,[80,571,493],{},[80,573,575],{"class":82,"line":574},25,[80,576,455],{"emptyLinePlaceholder":454},[80,578,580],{"class":82,"line":579},26,[80,581,582],{},"message DivideResponse {\n",[80,584,586],{"class":82,"line":585},27,[80,587,588],{},"  double result = 1;\n",[80,590,592],{"class":82,"line":591},28,[80,593,493],{},[15,595,596,597,600,601,604],{},"В ",[19,598,599],{},"calculator.pb.go"," будут Go-структуры сообщений. В ",[19,602,603],{},"calculator_grpc.pb.go"," будут gRPC-интерфейсы.",[15,606,607],{},"Разработчик реализует примерно такой интерфейс:",[27,609,612],{"className":610,"code":611,"language":87,"meta":33,"style":33},"language-go shiki shiki-themes github-dark","type CalculatorServiceServer interface {\n    Add(context.Context, *AddRequest) (*AddResponse, error)\n    Divide(context.Context, *DivideRequest) (*DivideResponse, error)\n}\n",[19,613,614,629,671,704],{"__ignoreMap":33},[80,615,616,620,623,626],{"class":82,"line":83},[80,617,619],{"class":618},"snl16","type",[80,621,622],{"class":86}," CalculatorServiceServer",[80,624,625],{"class":618}," interface",[80,627,628],{"class":214}," {\n",[80,630,631,634,637,640,643,646,649,652,655,658,660,663,665,668],{"class":82,"line":97},[80,632,633],{"class":86},"    Add",[80,635,636],{"class":214},"(",[80,638,639],{"class":86},"context",[80,641,642],{"class":214},".",[80,644,645],{"class":86},"Context",[80,647,648],{"class":214},", ",[80,650,651],{"class":618},"*",[80,653,654],{"class":86},"AddRequest",[80,656,657],{"class":214},") (",[80,659,651],{"class":618},[80,661,662],{"class":86},"AddResponse",[80,664,648],{"class":214},[80,666,667],{"class":618},"error",[80,669,670],{"class":214},")\n",[80,672,673,676,678,680,682,684,686,688,691,693,695,698,700,702],{"class":82,"line":133},[80,674,675],{"class":86},"    Divide",[80,677,636],{"class":214},[80,679,639],{"class":86},[80,681,642],{"class":214},[80,683,645],{"class":86},[80,685,648],{"class":214},[80,687,651],{"class":618},[80,689,690],{"class":86},"DivideRequest",[80,692,657],{"class":214},[80,694,651],{"class":618},[80,696,697],{"class":86},"DivideResponse",[80,699,648],{"class":214},[80,701,667],{"class":618},[80,703,670],{"class":214},[80,705,706],{"class":82,"line":144},[80,707,493],{"class":214},[15,709,710,711,713,714,717,718,721],{},"Точная сигнатура находится в generated-файле, но идея именно такая. В современных версиях ",[19,712,67],{}," server interface может содержать служебное требование, связанное с ",[19,715,716],{},"Unimplemented...Server",", поэтому не угадывайте интерфейс по памяти: откройте ",[19,719,720],{},"*_grpc.pb.go"," и реализуйте то, что сгенерировал ваш toolchain.",[37,723],{},[40,725,727],{"id":726},"структура-проекта","Структура проекта",[15,729,730],{},"Для маленького учебного сервиса достаточно такой раскладки:",[27,732,735],{"className":733,"code":734,"language":32,"meta":33},[30],"grpc-demo\u002F\n  go.mod\n  buf.yaml\n  buf.gen.yaml\n  proto\u002F\n    calculator\u002Fv1\u002Fcalculator.proto\n  gen\u002F\n    calculator\u002Fv1\u002Fcalculator.pb.go\n    calculator\u002Fv1\u002Fcalculator_grpc.pb.go\n  cmd\u002F\n    server\u002Fmain.go\n    client\u002Fmain.go\n  internal\u002F\n    calculator\u002F\n      server.go\n",[19,736,734],{"__ignoreMap":33},[15,738,739],{},"Граница простая:",[48,741,742,748,754,760,766],{},[51,743,744,747],{},[19,745,746],{},"proto\u002F"," - контракт, который читают люди и генераторы;",[51,749,750,753],{},[19,751,752],{},"gen\u002F"," - generated code, руками не правим;",[51,755,756,759],{},[19,757,758],{},"internal\u002Fcalculator\u002Fserver.go"," - реализация generated server interface;",[51,761,762,765],{},[19,763,764],{},"cmd\u002Fserver"," - сборка gRPC server: listener, interceptors, регистрация сервисов;",[51,767,768,771],{},[19,769,770],{},"cmd\u002Fclient"," - локальный клиент для ручной проверки.",[15,773,774,775,778],{},"Такой layout помогает не превращать ",[19,776,777],{},"main.go"," в смесь transport setup, бизнес-логики и protobuf-маппинга.",[37,780],{},[40,782,784],{"id":783},"реализация-сервера","Реализация сервера",[27,786,789],{"className":610,"code":787,"language":87,"meta":788,"style":33},"package main\n\nimport (\n    \"context\"\n    \"log\"\n    \"net\"\n\n    calculatorv1 \"example.com\u002Fgrpc-demo\u002Fproto\u002Fcalculator\u002Fv1\"\n    \"google.golang.org\u002Fgrpc\"\n    \"google.golang.org\u002Fgrpc\u002Fcodes\"\n    \"google.golang.org\u002Fgrpc\u002Fstatus\"\n)\n\ntype CalculatorServer struct {\n    calculatorv1.UnimplementedCalculatorServiceServer\n}\n\nfunc (s *CalculatorServer) Add(\n    ctx context.Context,\n    req *calculatorv1.AddRequest,\n) (*calculatorv1.AddResponse, error) {\n    return &calculatorv1.AddResponse{\n        Result: req.GetA() + req.GetB(),\n    }, nil\n}\n\nfunc (s *CalculatorServer) Divide(\n    ctx context.Context,\n    req *calculatorv1.DivideRequest,\n) (*calculatorv1.DivideResponse, error) {\n    if req.GetDivisor() == 0 {\n        return nil, status.Error(codes.InvalidArgument, \"division by zero\")\n    }\n\n    return &calculatorv1.DivideResponse{\n        Result: float64(req.GetDividend()) \u002F float64(req.GetDivisor()),\n    }, nil\n}\n\nfunc main() {\n    lis, err := net.Listen(\"tcp\", \":50051\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    grpcServer := grpc.NewServer()\n    calculatorv1.RegisterCalculatorServiceServer(grpcServer, &CalculatorServer{})\n\n    log.Println(\"gRPC server listening on :50051\")\n    if err := grpcServer.Serve(lis); err != nil {\n        log.Fatal(err)\n    }\n}\n","no-run",[19,790,791,799,803,811,821,830,839,843,856,865,874,883,887,891,903,913,917,921,947,962,979,998,1015,1038,1046,1050,1054,1073,1085,1100,1119,1140,1163,1169,1174,1189,1220,1227,1232,1237,1248,1275,1290,1302,1307,1312,1329,1349,1354,1370,1394,1403,1408],{"__ignoreMap":33},[80,792,793,796],{"class":82,"line":83},[80,794,795],{"class":618},"package",[80,797,798],{"class":86}," main\n",[80,800,801],{"class":82,"line":97},[80,802,455],{"emptyLinePlaceholder":454},[80,804,805,808],{"class":82,"line":133},[80,806,807],{"class":618},"import",[80,809,810],{"class":214}," (\n",[80,812,813,816,818],{"class":82,"line":144},[80,814,815],{"class":90},"    \"",[80,817,639],{"class":86},[80,819,820],{"class":90},"\"\n",[80,822,823,825,828],{"class":82,"line":249},[80,824,815],{"class":90},[80,826,827],{"class":86},"log",[80,829,820],{"class":90},[80,831,832,834,837],{"class":82,"line":257},[80,833,815],{"class":90},[80,835,836],{"class":86},"net",[80,838,820],{"class":90},[80,840,841],{"class":82,"line":266},[80,842,455],{"emptyLinePlaceholder":454},[80,844,845,848,851,854],{"class":82,"line":274},[80,846,847],{"class":214},"    calculatorv1 ",[80,849,850],{"class":90},"\"",[80,852,853],{"class":86},"example.com\u002Fgrpc-demo\u002Fproto\u002Fcalculator\u002Fv1",[80,855,820],{"class":90},[80,857,858,860,863],{"class":82,"line":281},[80,859,815],{"class":90},[80,861,862],{"class":86},"google.golang.org\u002Fgrpc",[80,864,820],{"class":90},[80,866,867,869,872],{"class":82,"line":376},[80,868,815],{"class":90},[80,870,871],{"class":86},"google.golang.org\u002Fgrpc\u002Fcodes",[80,873,820],{"class":90},[80,875,876,878,881],{"class":82,"line":496},[80,877,815],{"class":90},[80,879,880],{"class":86},"google.golang.org\u002Fgrpc\u002Fstatus",[80,882,820],{"class":90},[80,884,885],{"class":82,"line":501},[80,886,670],{"class":214},[80,888,889],{"class":82,"line":507},[80,890,455],{"emptyLinePlaceholder":454},[80,892,893,895,898,901],{"class":82,"line":513},[80,894,619],{"class":618},[80,896,897],{"class":86}," CalculatorServer",[80,899,900],{"class":618}," struct",[80,902,628],{"class":214},[80,904,905,908,910],{"class":82,"line":519},[80,906,907],{"class":86},"    calculatorv1",[80,909,642],{"class":214},[80,911,912],{"class":86},"UnimplementedCalculatorServiceServer\n",[80,914,915],{"class":82,"line":524},[80,916,493],{"class":214},[80,918,919],{"class":82,"line":529},[80,920,455],{"emptyLinePlaceholder":454},[80,922,923,926,929,933,935,938,941,944],{"class":82,"line":535},[80,924,925],{"class":618},"func",[80,927,928],{"class":214}," (",[80,930,932],{"class":931},"s9osk","s ",[80,934,651],{"class":618},[80,936,937],{"class":86},"CalculatorServer",[80,939,940],{"class":214},") ",[80,942,943],{"class":86},"Add",[80,945,946],{"class":214},"(\n",[80,948,949,952,955,957,959],{"class":82,"line":541},[80,950,951],{"class":931},"    ctx",[80,953,954],{"class":86}," context",[80,956,642],{"class":214},[80,958,645],{"class":86},[80,960,961],{"class":214},",\n",[80,963,964,967,970,973,975,977],{"class":82,"line":546},[80,965,966],{"class":931},"    req",[80,968,969],{"class":618}," *",[80,971,972],{"class":86},"calculatorv1",[80,974,642],{"class":214},[80,976,654],{"class":86},[80,978,961],{"class":214},[80,980,981,983,985,987,989,991,993,995],{"class":82,"line":551},[80,982,657],{"class":214},[80,984,651],{"class":618},[80,986,972],{"class":86},[80,988,642],{"class":214},[80,990,662],{"class":86},[80,992,648],{"class":214},[80,994,667],{"class":618},[80,996,997],{"class":214},") {\n",[80,999,1000,1003,1006,1008,1010,1012],{"class":82,"line":557},[80,1001,1002],{"class":618},"    return",[80,1004,1005],{"class":618}," &",[80,1007,972],{"class":86},[80,1009,642],{"class":214},[80,1011,662],{"class":86},[80,1013,1014],{"class":214},"{\n",[80,1016,1017,1020,1023,1026,1029,1032,1035],{"class":82,"line":563},[80,1018,1019],{"class":214},"        Result: req.",[80,1021,1022],{"class":86},"GetA",[80,1024,1025],{"class":214},"() ",[80,1027,1028],{"class":618},"+",[80,1030,1031],{"class":214}," req.",[80,1033,1034],{"class":86},"GetB",[80,1036,1037],{"class":214},"(),\n",[80,1039,1040,1043],{"class":82,"line":569},[80,1041,1042],{"class":214},"    }, ",[80,1044,1045],{"class":119},"nil\n",[80,1047,1048],{"class":82,"line":574},[80,1049,493],{"class":214},[80,1051,1052],{"class":82,"line":579},[80,1053,455],{"emptyLinePlaceholder":454},[80,1055,1056,1058,1060,1062,1064,1066,1068,1071],{"class":82,"line":585},[80,1057,925],{"class":618},[80,1059,928],{"class":214},[80,1061,932],{"class":931},[80,1063,651],{"class":618},[80,1065,937],{"class":86},[80,1067,940],{"class":214},[80,1069,1070],{"class":86},"Divide",[80,1072,946],{"class":214},[80,1074,1075,1077,1079,1081,1083],{"class":82,"line":591},[80,1076,951],{"class":931},[80,1078,954],{"class":86},[80,1080,642],{"class":214},[80,1082,645],{"class":86},[80,1084,961],{"class":214},[80,1086,1088,1090,1092,1094,1096,1098],{"class":82,"line":1087},29,[80,1089,966],{"class":931},[80,1091,969],{"class":618},[80,1093,972],{"class":86},[80,1095,642],{"class":214},[80,1097,690],{"class":86},[80,1099,961],{"class":214},[80,1101,1103,1105,1107,1109,1111,1113,1115,1117],{"class":82,"line":1102},30,[80,1104,657],{"class":214},[80,1106,651],{"class":618},[80,1108,972],{"class":86},[80,1110,642],{"class":214},[80,1112,697],{"class":86},[80,1114,648],{"class":214},[80,1116,667],{"class":618},[80,1118,997],{"class":214},[80,1120,1122,1125,1127,1130,1132,1135,1138],{"class":82,"line":1121},31,[80,1123,1124],{"class":618},"    if",[80,1126,1031],{"class":214},[80,1128,1129],{"class":86},"GetDivisor",[80,1131,1025],{"class":214},[80,1133,1134],{"class":618},"==",[80,1136,1137],{"class":119}," 0",[80,1139,628],{"class":214},[80,1141,1143,1146,1149,1152,1155,1158,1161],{"class":82,"line":1142},32,[80,1144,1145],{"class":618},"        return",[80,1147,1148],{"class":119}," nil",[80,1150,1151],{"class":214},", status.",[80,1153,1154],{"class":86},"Error",[80,1156,1157],{"class":214},"(codes.InvalidArgument, ",[80,1159,1160],{"class":90},"\"division by zero\"",[80,1162,670],{"class":214},[80,1164,1166],{"class":82,"line":1165},33,[80,1167,1168],{"class":214},"    }\n",[80,1170,1172],{"class":82,"line":1171},34,[80,1173,455],{"emptyLinePlaceholder":454},[80,1175,1177,1179,1181,1183,1185,1187],{"class":82,"line":1176},35,[80,1178,1002],{"class":618},[80,1180,1005],{"class":618},[80,1182,972],{"class":86},[80,1184,642],{"class":214},[80,1186,697],{"class":86},[80,1188,1014],{"class":214},[80,1190,1192,1195,1198,1201,1204,1207,1210,1213,1215,1217],{"class":82,"line":1191},36,[80,1193,1194],{"class":214},"        Result: ",[80,1196,1197],{"class":618},"float64",[80,1199,1200],{"class":214},"(req.",[80,1202,1203],{"class":86},"GetDividend",[80,1205,1206],{"class":214},"()) ",[80,1208,1209],{"class":618},"\u002F",[80,1211,1212],{"class":618}," float64",[80,1214,1200],{"class":214},[80,1216,1129],{"class":86},[80,1218,1219],{"class":214},"()),\n",[80,1221,1223,1225],{"class":82,"line":1222},37,[80,1224,1042],{"class":214},[80,1226,1045],{"class":119},[80,1228,1230],{"class":82,"line":1229},38,[80,1231,493],{"class":214},[80,1233,1235],{"class":82,"line":1234},39,[80,1236,455],{"emptyLinePlaceholder":454},[80,1238,1240,1242,1245],{"class":82,"line":1239},40,[80,1241,925],{"class":618},[80,1243,1244],{"class":86}," main",[80,1246,1247],{"class":214},"() {\n",[80,1249,1251,1254,1257,1260,1263,1265,1268,1270,1273],{"class":82,"line":1250},41,[80,1252,1253],{"class":214},"    lis, err ",[80,1255,1256],{"class":618},":=",[80,1258,1259],{"class":214}," net.",[80,1261,1262],{"class":86},"Listen",[80,1264,636],{"class":214},[80,1266,1267],{"class":90},"\"tcp\"",[80,1269,648],{"class":214},[80,1271,1272],{"class":90},"\":50051\"",[80,1274,670],{"class":214},[80,1276,1278,1280,1283,1286,1288],{"class":82,"line":1277},42,[80,1279,1124],{"class":618},[80,1281,1282],{"class":214}," err ",[80,1284,1285],{"class":618},"!=",[80,1287,1148],{"class":119},[80,1289,628],{"class":214},[80,1291,1293,1296,1299],{"class":82,"line":1292},43,[80,1294,1295],{"class":214},"        log.",[80,1297,1298],{"class":86},"Fatal",[80,1300,1301],{"class":214},"(err)\n",[80,1303,1305],{"class":82,"line":1304},44,[80,1306,1168],{"class":214},[80,1308,1310],{"class":82,"line":1309},45,[80,1311,455],{"emptyLinePlaceholder":454},[80,1313,1315,1318,1320,1323,1326],{"class":82,"line":1314},46,[80,1316,1317],{"class":214},"    grpcServer ",[80,1319,1256],{"class":618},[80,1321,1322],{"class":214}," grpc.",[80,1324,1325],{"class":86},"NewServer",[80,1327,1328],{"class":214},"()\n",[80,1330,1332,1335,1338,1341,1344,1346],{"class":82,"line":1331},47,[80,1333,1334],{"class":214},"    calculatorv1.",[80,1336,1337],{"class":86},"RegisterCalculatorServiceServer",[80,1339,1340],{"class":214},"(grpcServer, ",[80,1342,1343],{"class":618},"&",[80,1345,937],{"class":86},[80,1347,1348],{"class":214},"{})\n",[80,1350,1352],{"class":82,"line":1351},48,[80,1353,455],{"emptyLinePlaceholder":454},[80,1355,1357,1360,1363,1365,1368],{"class":82,"line":1356},49,[80,1358,1359],{"class":214},"    log.",[80,1361,1362],{"class":86},"Println",[80,1364,636],{"class":214},[80,1366,1367],{"class":90},"\"gRPC server listening on :50051\"",[80,1369,670],{"class":214},[80,1371,1373,1375,1377,1379,1382,1385,1388,1390,1392],{"class":82,"line":1372},50,[80,1374,1124],{"class":618},[80,1376,1282],{"class":214},[80,1378,1256],{"class":618},[80,1380,1381],{"class":214}," grpcServer.",[80,1383,1384],{"class":86},"Serve",[80,1386,1387],{"class":214},"(lis); err ",[80,1389,1285],{"class":618},[80,1391,1148],{"class":119},[80,1393,628],{"class":214},[80,1395,1397,1399,1401],{"class":82,"line":1396},51,[80,1398,1295],{"class":214},[80,1400,1298],{"class":86},[80,1402,1301],{"class":214},[80,1404,1406],{"class":82,"line":1405},52,[80,1407,1168],{"class":214},[80,1409,1411],{"class":82,"line":1410},53,[80,1412,493],{"class":214},[15,1414,1415,1418,1419,1421],{},[19,1416,1417],{},"UnimplementedCalculatorServiceServer"," встраивают, чтобы сервер был совместим с будущими добавлениями методов в generated interface. Если в ",[19,1420,21],{}," появится новый RPC, код не сломается совсем неожиданно, а gRPC вернёт корректный \"unimplemented\" для метода, который вы ещё не реализовали.",[15,1423,1424,1425,1428],{},"В production-коде ",[19,1426,1427],{},"main"," обычно делает чуть больше:",[48,1430,1431,1434,1437,1440,1443],{},[51,1432,1433],{},"создаёт logger\u002Fconfig;",[51,1435,1436],{},"собирает usecase и repositories;",[51,1438,1439],{},"настраивает unary\u002Fstream interceptors;",[51,1441,1442],{},"регистрирует health\u002Freflection для dev-среды;",[51,1444,1445],{},"корректно останавливает сервер по сигналу.",[158,1447,1449],{"id":1448},"graceful-shutdown","Graceful shutdown",[15,1451,1452,1454,1455,1209,1458,198],{},[19,1453,1384],{}," блокирует текущую горутину. Чтобы приложение завершалось аккуратно, сервер запускают отдельно и слушают ",[19,1456,1457],{},"SIGINT",[19,1459,1460],{},"SIGTERM",[27,1462,1464],{"className":610,"code":1463,"language":87,"meta":33,"style":33},"ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)\ndefer stop()\n\ngrpcServer := grpc.NewServer()\ncalculatorv1.RegisterCalculatorServiceServer(grpcServer, &CalculatorServer{})\n\nserveErr := make(chan error, 1)\ngo func() {\n    serveErr \u003C- grpcServer.Serve(lis)\n}()\n\nselect {\ncase \u003C-ctx.Done():\n    log.Println(\"shutting down grpc server\")\n\n    done := make(chan struct{})\n    go func() {\n        grpcServer.GracefulStop()\n        close(done)\n    }()\n\n    select {\n    case \u003C-done:\n    case \u003C-time.After(10 * time.Second):\n        grpcServer.Stop()\n    }\n\ncase err := \u003C-serveErr:\n    if err != nil {\n        log.Fatal(err)\n    }\n}\n",[19,1465,1466,1488,1498,1502,1515,1530,1534,1559,1568,1583,1588,1592,1599,1616,1629,1633,1650,1659,1669,1677,1682,1686,1693,1703,1725,1734,1738,1742,1755,1767,1775,1779],{"__ignoreMap":33},[80,1467,1468,1471,1473,1476,1479,1482,1485],{"class":82,"line":83},[80,1469,1470],{"class":214},"ctx, stop ",[80,1472,1256],{"class":618},[80,1474,1475],{"class":214}," signal.",[80,1477,1478],{"class":86},"NotifyContext",[80,1480,1481],{"class":214},"(context.",[80,1483,1484],{"class":86},"Background",[80,1486,1487],{"class":214},"(), os.Interrupt, syscall.SIGTERM)\n",[80,1489,1490,1493,1496],{"class":82,"line":97},[80,1491,1492],{"class":618},"defer",[80,1494,1495],{"class":86}," stop",[80,1497,1328],{"class":214},[80,1499,1500],{"class":82,"line":133},[80,1501,455],{"emptyLinePlaceholder":454},[80,1503,1504,1507,1509,1511,1513],{"class":82,"line":144},[80,1505,1506],{"class":214},"grpcServer ",[80,1508,1256],{"class":618},[80,1510,1322],{"class":214},[80,1512,1325],{"class":86},[80,1514,1328],{"class":214},[80,1516,1517,1520,1522,1524,1526,1528],{"class":82,"line":249},[80,1518,1519],{"class":214},"calculatorv1.",[80,1521,1337],{"class":86},[80,1523,1340],{"class":214},[80,1525,1343],{"class":618},[80,1527,937],{"class":86},[80,1529,1348],{"class":214},[80,1531,1532],{"class":82,"line":257},[80,1533,455],{"emptyLinePlaceholder":454},[80,1535,1536,1539,1541,1544,1546,1549,1552,1554,1557],{"class":82,"line":266},[80,1537,1538],{"class":214},"serveErr ",[80,1540,1256],{"class":618},[80,1542,1543],{"class":86}," make",[80,1545,636],{"class":214},[80,1547,1548],{"class":618},"chan",[80,1550,1551],{"class":618}," error",[80,1553,648],{"class":214},[80,1555,1556],{"class":119},"1",[80,1558,670],{"class":214},[80,1560,1561,1563,1566],{"class":82,"line":274},[80,1562,87],{"class":618},[80,1564,1565],{"class":618}," func",[80,1567,1247],{"class":214},[80,1569,1570,1573,1576,1578,1580],{"class":82,"line":281},[80,1571,1572],{"class":214},"    serveErr ",[80,1574,1575],{"class":618},"\u003C-",[80,1577,1381],{"class":214},[80,1579,1384],{"class":86},[80,1581,1582],{"class":214},"(lis)\n",[80,1584,1585],{"class":82,"line":376},[80,1586,1587],{"class":214},"}()\n",[80,1589,1590],{"class":82,"line":496},[80,1591,455],{"emptyLinePlaceholder":454},[80,1593,1594,1597],{"class":82,"line":501},[80,1595,1596],{"class":618},"select",[80,1598,628],{"class":214},[80,1600,1601,1604,1607,1610,1613],{"class":82,"line":507},[80,1602,1603],{"class":618},"case",[80,1605,1606],{"class":618}," \u003C-",[80,1608,1609],{"class":214},"ctx.",[80,1611,1612],{"class":86},"Done",[80,1614,1615],{"class":214},"():\n",[80,1617,1618,1620,1622,1624,1627],{"class":82,"line":513},[80,1619,1359],{"class":214},[80,1621,1362],{"class":86},[80,1623,636],{"class":214},[80,1625,1626],{"class":90},"\"shutting down grpc server\"",[80,1628,670],{"class":214},[80,1630,1631],{"class":82,"line":519},[80,1632,455],{"emptyLinePlaceholder":454},[80,1634,1635,1638,1640,1642,1644,1646,1648],{"class":82,"line":524},[80,1636,1637],{"class":214},"    done ",[80,1639,1256],{"class":618},[80,1641,1543],{"class":86},[80,1643,636],{"class":214},[80,1645,1548],{"class":618},[80,1647,900],{"class":618},[80,1649,1348],{"class":214},[80,1651,1652,1655,1657],{"class":82,"line":529},[80,1653,1654],{"class":618},"    go",[80,1656,1565],{"class":618},[80,1658,1247],{"class":214},[80,1660,1661,1664,1667],{"class":82,"line":535},[80,1662,1663],{"class":214},"        grpcServer.",[80,1665,1666],{"class":86},"GracefulStop",[80,1668,1328],{"class":214},[80,1670,1671,1674],{"class":82,"line":541},[80,1672,1673],{"class":86},"        close",[80,1675,1676],{"class":214},"(done)\n",[80,1678,1679],{"class":82,"line":546},[80,1680,1681],{"class":214},"    }()\n",[80,1683,1684],{"class":82,"line":551},[80,1685,455],{"emptyLinePlaceholder":454},[80,1687,1688,1691],{"class":82,"line":557},[80,1689,1690],{"class":618},"    select",[80,1692,628],{"class":214},[80,1694,1695,1698,1700],{"class":82,"line":563},[80,1696,1697],{"class":618},"    case",[80,1699,1606],{"class":618},[80,1701,1702],{"class":214},"done:\n",[80,1704,1705,1707,1709,1712,1715,1717,1720,1722],{"class":82,"line":569},[80,1706,1697],{"class":618},[80,1708,1606],{"class":618},[80,1710,1711],{"class":214},"time.",[80,1713,1714],{"class":86},"After",[80,1716,636],{"class":214},[80,1718,1719],{"class":119},"10",[80,1721,969],{"class":618},[80,1723,1724],{"class":214}," time.Second):\n",[80,1726,1727,1729,1732],{"class":82,"line":574},[80,1728,1663],{"class":214},[80,1730,1731],{"class":86},"Stop",[80,1733,1328],{"class":214},[80,1735,1736],{"class":82,"line":579},[80,1737,1168],{"class":214},[80,1739,1740],{"class":82,"line":585},[80,1741,455],{"emptyLinePlaceholder":454},[80,1743,1744,1746,1748,1750,1752],{"class":82,"line":591},[80,1745,1603],{"class":618},[80,1747,1282],{"class":214},[80,1749,1256],{"class":618},[80,1751,1606],{"class":618},[80,1753,1754],{"class":214},"serveErr:\n",[80,1756,1757,1759,1761,1763,1765],{"class":82,"line":1087},[80,1758,1124],{"class":618},[80,1760,1282],{"class":214},[80,1762,1285],{"class":618},[80,1764,1148],{"class":119},[80,1766,628],{"class":214},[80,1768,1769,1771,1773],{"class":82,"line":1102},[80,1770,1295],{"class":214},[80,1772,1298],{"class":86},[80,1774,1301],{"class":214},[80,1776,1777],{"class":82,"line":1121},[80,1778,1168],{"class":214},[80,1780,1781],{"class":82,"line":1142},[80,1782,493],{"class":214},[15,1784,1785,1787,1788,1790],{},[19,1786,1666],{}," перестаёт принимать новые соединения и ждёт активные RPC. ",[19,1789,1731],{}," рубит сразу. Поэтому обычно дают небольшой timeout: нормальные запросы успевают завершиться, зависшие не держат процесс бесконечно.",[37,1792],{},[40,1794,1796],{"id":1795},"клиент","Клиент",[27,1798,1800],{"className":610,"code":1799,"language":87,"meta":788,"style":33},"package main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"log\"\n    \"time\"\n\n    calculatorv1 \"example.com\u002Fgrpc-demo\u002Fproto\u002Fcalculator\u002Fv1\"\n    \"google.golang.org\u002Fgrpc\"\n    \"google.golang.org\u002Fgrpc\u002Fcredentials\u002Finsecure\"\n)\n\nfunc main() {\n    conn, err := grpc.NewClient(\n        \"localhost:50051\",\n        grpc.WithTransportCredentials(insecure.NewCredentials()),\n    )\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer conn.Close()\n\n    client := calculatorv1.NewCalculatorServiceClient(conn)\n\n    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n    defer cancel()\n\n    resp, err := client.Add(ctx, &calculatorv1.AddRequest{\n        A: 40,\n        B: 2,\n    })\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    fmt.Println(resp.GetResult())\n}\n",[19,1801,1802,1808,1812,1818,1826,1835,1843,1852,1856,1866,1874,1883,1887,1891,1899,1913,1920,1936,1941,1953,1961,1965,1978,1982,1998,2002,2030,2039,2043,2068,2078,2087,2092,2104,2112,2116,2120,2136],{"__ignoreMap":33},[80,1803,1804,1806],{"class":82,"line":83},[80,1805,795],{"class":618},[80,1807,798],{"class":86},[80,1809,1810],{"class":82,"line":97},[80,1811,455],{"emptyLinePlaceholder":454},[80,1813,1814,1816],{"class":82,"line":133},[80,1815,807],{"class":618},[80,1817,810],{"class":214},[80,1819,1820,1822,1824],{"class":82,"line":144},[80,1821,815],{"class":90},[80,1823,639],{"class":86},[80,1825,820],{"class":90},[80,1827,1828,1830,1833],{"class":82,"line":249},[80,1829,815],{"class":90},[80,1831,1832],{"class":86},"fmt",[80,1834,820],{"class":90},[80,1836,1837,1839,1841],{"class":82,"line":257},[80,1838,815],{"class":90},[80,1840,827],{"class":86},[80,1842,820],{"class":90},[80,1844,1845,1847,1850],{"class":82,"line":266},[80,1846,815],{"class":90},[80,1848,1849],{"class":86},"time",[80,1851,820],{"class":90},[80,1853,1854],{"class":82,"line":274},[80,1855,455],{"emptyLinePlaceholder":454},[80,1857,1858,1860,1862,1864],{"class":82,"line":281},[80,1859,847],{"class":214},[80,1861,850],{"class":90},[80,1863,853],{"class":86},[80,1865,820],{"class":90},[80,1867,1868,1870,1872],{"class":82,"line":376},[80,1869,815],{"class":90},[80,1871,862],{"class":86},[80,1873,820],{"class":90},[80,1875,1876,1878,1881],{"class":82,"line":496},[80,1877,815],{"class":90},[80,1879,1880],{"class":86},"google.golang.org\u002Fgrpc\u002Fcredentials\u002Finsecure",[80,1882,820],{"class":90},[80,1884,1885],{"class":82,"line":501},[80,1886,670],{"class":214},[80,1888,1889],{"class":82,"line":507},[80,1890,455],{"emptyLinePlaceholder":454},[80,1892,1893,1895,1897],{"class":82,"line":513},[80,1894,925],{"class":618},[80,1896,1244],{"class":86},[80,1898,1247],{"class":214},[80,1900,1901,1904,1906,1908,1911],{"class":82,"line":519},[80,1902,1903],{"class":214},"    conn, err ",[80,1905,1256],{"class":618},[80,1907,1322],{"class":214},[80,1909,1910],{"class":86},"NewClient",[80,1912,946],{"class":214},[80,1914,1915,1918],{"class":82,"line":524},[80,1916,1917],{"class":90},"        \"localhost:50051\"",[80,1919,961],{"class":214},[80,1921,1922,1925,1928,1931,1934],{"class":82,"line":529},[80,1923,1924],{"class":214},"        grpc.",[80,1926,1927],{"class":86},"WithTransportCredentials",[80,1929,1930],{"class":214},"(insecure.",[80,1932,1933],{"class":86},"NewCredentials",[80,1935,1219],{"class":214},[80,1937,1938],{"class":82,"line":535},[80,1939,1940],{"class":214},"    )\n",[80,1942,1943,1945,1947,1949,1951],{"class":82,"line":541},[80,1944,1124],{"class":618},[80,1946,1282],{"class":214},[80,1948,1285],{"class":618},[80,1950,1148],{"class":119},[80,1952,628],{"class":214},[80,1954,1955,1957,1959],{"class":82,"line":546},[80,1956,1295],{"class":214},[80,1958,1298],{"class":86},[80,1960,1301],{"class":214},[80,1962,1963],{"class":82,"line":551},[80,1964,1168],{"class":214},[80,1966,1967,1970,1973,1976],{"class":82,"line":557},[80,1968,1969],{"class":618},"    defer",[80,1971,1972],{"class":214}," conn.",[80,1974,1975],{"class":86},"Close",[80,1977,1328],{"class":214},[80,1979,1980],{"class":82,"line":563},[80,1981,455],{"emptyLinePlaceholder":454},[80,1983,1984,1987,1989,1992,1995],{"class":82,"line":569},[80,1985,1986],{"class":214},"    client ",[80,1988,1256],{"class":618},[80,1990,1991],{"class":214}," calculatorv1.",[80,1993,1994],{"class":86},"NewCalculatorServiceClient",[80,1996,1997],{"class":214},"(conn)\n",[80,1999,2000],{"class":82,"line":574},[80,2001,455],{"emptyLinePlaceholder":454},[80,2003,2004,2007,2009,2012,2015,2017,2019,2022,2025,2027],{"class":82,"line":579},[80,2005,2006],{"class":214},"    ctx, cancel ",[80,2008,1256],{"class":618},[80,2010,2011],{"class":214}," context.",[80,2013,2014],{"class":86},"WithTimeout",[80,2016,1481],{"class":214},[80,2018,1484],{"class":86},[80,2020,2021],{"class":214},"(), ",[80,2023,2024],{"class":119},"2",[80,2026,651],{"class":618},[80,2028,2029],{"class":214},"time.Second)\n",[80,2031,2032,2034,2037],{"class":82,"line":585},[80,2033,1969],{"class":618},[80,2035,2036],{"class":86}," cancel",[80,2038,1328],{"class":214},[80,2040,2041],{"class":82,"line":591},[80,2042,455],{"emptyLinePlaceholder":454},[80,2044,2045,2048,2050,2053,2055,2058,2060,2062,2064,2066],{"class":82,"line":1087},[80,2046,2047],{"class":214},"    resp, err ",[80,2049,1256],{"class":618},[80,2051,2052],{"class":214}," client.",[80,2054,943],{"class":86},[80,2056,2057],{"class":214},"(ctx, ",[80,2059,1343],{"class":618},[80,2061,972],{"class":86},[80,2063,642],{"class":214},[80,2065,654],{"class":86},[80,2067,1014],{"class":214},[80,2069,2070,2073,2076],{"class":82,"line":1102},[80,2071,2072],{"class":214},"        A: ",[80,2074,2075],{"class":119},"40",[80,2077,961],{"class":214},[80,2079,2080,2083,2085],{"class":82,"line":1121},[80,2081,2082],{"class":214},"        B: ",[80,2084,2024],{"class":119},[80,2086,961],{"class":214},[80,2088,2089],{"class":82,"line":1142},[80,2090,2091],{"class":214},"    })\n",[80,2093,2094,2096,2098,2100,2102],{"class":82,"line":1165},[80,2095,1124],{"class":618},[80,2097,1282],{"class":214},[80,2099,1285],{"class":618},[80,2101,1148],{"class":119},[80,2103,628],{"class":214},[80,2105,2106,2108,2110],{"class":82,"line":1171},[80,2107,1295],{"class":214},[80,2109,1298],{"class":86},[80,2111,1301],{"class":214},[80,2113,2114],{"class":82,"line":1176},[80,2115,1168],{"class":214},[80,2117,2118],{"class":82,"line":1191},[80,2119,455],{"emptyLinePlaceholder":454},[80,2121,2122,2125,2127,2130,2133],{"class":82,"line":1222},[80,2123,2124],{"class":214},"    fmt.",[80,2126,1362],{"class":86},[80,2128,2129],{"class":214},"(resp.",[80,2131,2132],{"class":86},"GetResult",[80,2134,2135],{"class":214},"())\n",[80,2137,2138],{"class":82,"line":1229},[80,2139,493],{"class":214},[15,2141,2142,2145],{},[19,2143,2144],{},"insecure.NewCredentials()"," допустим только для локального примера. В реальных сервисах обычно используют TLS или mesh\u002Finfra-уровень, который обеспечивает защищённый транспорт.",[15,2147,2148,2149,2152],{},"В старых примерах часто встречается ",[19,2150,2151],{},"grpc.Dial",". В новом коде ориентируйтесь на актуальную документацию grpc-go и стиль версии, которую использует проект.",[15,2154,2155,2156,2159,2160,2163,2164,2166],{},"Для нового tutorial-кода в grpc-go начиная с v1.63 ориентируйтесь на ",[19,2157,2158],{},"grpc.NewClient",". Он создаёт ",[19,2161,2162],{},"ClientConn"," как виртуальное соединение: реальный network connection устанавливается лениво при RPC, а при обрывах ",[19,2165,2162],{}," сам управляет reconnect'ами. Поэтому обычно не нужно вручную \"переподключать клиента\" на каждый запрос.",[37,2168],{},[40,2170,2172,2173],{"id":2171},"локальная-проверка-без-curl","Локальная проверка без ",[19,2174,2175],{},"curl",[15,2177,2178],{},"Самый надёжный способ проверить gRPC локально - написать маленький generated client. Он использует тот же контракт, что и настоящий потребитель:",[27,2180,2182],{"className":74,"code":2181,"language":76,"meta":33,"style":33},"go run .\u002Fcmd\u002Fserver\n",[19,2183,2184],{"__ignoreMap":33},[80,2185,2186,2188,2191],{"class":82,"line":83},[80,2187,87],{"class":86},[80,2189,2190],{"class":90}," run",[80,2192,2193],{"class":90}," .\u002Fcmd\u002Fserver\n",[15,2195,2196],{},"В другом терминале:",[27,2198,2200],{"className":74,"code":2199,"language":76,"meta":33,"style":33},"go run .\u002Fcmd\u002Fclient\n",[19,2201,2202],{"__ignoreMap":33},[80,2203,2204,2206,2208],{"class":82,"line":83},[80,2205,87],{"class":86},[80,2207,2190],{"class":90},[80,2209,2210],{"class":90}," .\u002Fcmd\u002Fclient\n",[15,2212,2213],{},"Для автоматической проверки можно поднять сервер на случайном порту в тесте:",[27,2215,2217],{"className":610,"code":2216,"language":87,"meta":33,"style":33},"func TestCalculatorAdd(t *testing.T) {\n    lis, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    srv := grpc.NewServer()\n    calculatorv1.RegisterCalculatorServiceServer(srv, &CalculatorServer{})\n\n    go func() {\n        _ = srv.Serve(lis)\n    }()\n    t.Cleanup(srv.Stop)\n\n    conn, err := grpc.NewClient(\n        lis.Addr().String(),\n        grpc.WithTransportCredentials(insecure.NewCredentials()),\n    )\n    if err != nil {\n        t.Fatal(err)\n    }\n    t.Cleanup(func() { _ = conn.Close() })\n\n    client := calculatorv1.NewCalculatorServiceClient(conn)\n    resp, err := client.Add(context.Background(), &calculatorv1.AddRequest{A: 40, B: 2})\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    if resp.GetResult() != 42 {\n        t.Fatalf(\"result = %d, want 42\", resp.GetResult())\n    }\n}\n",[19,2218,2219,2243,2264,2276,2285,2289,2293,2306,2321,2325,2333,2348,2352,2363,2367,2379,2395,2407,2411,2423,2431,2435,2457,2461,2473,2510,2522,2530,2534,2538,2556,2581,2585],{"__ignoreMap":33},[80,2220,2221,2223,2226,2228,2231,2233,2236,2238,2241],{"class":82,"line":83},[80,2222,925],{"class":618},[80,2224,2225],{"class":86}," TestCalculatorAdd",[80,2227,636],{"class":214},[80,2229,2230],{"class":931},"t",[80,2232,969],{"class":618},[80,2234,2235],{"class":86},"testing",[80,2237,642],{"class":214},[80,2239,2240],{"class":86},"T",[80,2242,997],{"class":214},[80,2244,2245,2247,2249,2251,2253,2255,2257,2259,2262],{"class":82,"line":97},[80,2246,1253],{"class":214},[80,2248,1256],{"class":618},[80,2250,1259],{"class":214},[80,2252,1262],{"class":86},[80,2254,636],{"class":214},[80,2256,1267],{"class":90},[80,2258,648],{"class":214},[80,2260,2261],{"class":90},"\"127.0.0.1:0\"",[80,2263,670],{"class":214},[80,2265,2266,2268,2270,2272,2274],{"class":82,"line":133},[80,2267,1124],{"class":618},[80,2269,1282],{"class":214},[80,2271,1285],{"class":618},[80,2273,1148],{"class":119},[80,2275,628],{"class":214},[80,2277,2278,2281,2283],{"class":82,"line":144},[80,2279,2280],{"class":214},"        t.",[80,2282,1298],{"class":86},[80,2284,1301],{"class":214},[80,2286,2287],{"class":82,"line":249},[80,2288,1168],{"class":214},[80,2290,2291],{"class":82,"line":257},[80,2292,455],{"emptyLinePlaceholder":454},[80,2294,2295,2298,2300,2302,2304],{"class":82,"line":266},[80,2296,2297],{"class":214},"    srv ",[80,2299,1256],{"class":618},[80,2301,1322],{"class":214},[80,2303,1325],{"class":86},[80,2305,1328],{"class":214},[80,2307,2308,2310,2312,2315,2317,2319],{"class":82,"line":274},[80,2309,1334],{"class":214},[80,2311,1337],{"class":86},[80,2313,2314],{"class":214},"(srv, ",[80,2316,1343],{"class":618},[80,2318,937],{"class":86},[80,2320,1348],{"class":214},[80,2322,2323],{"class":82,"line":281},[80,2324,455],{"emptyLinePlaceholder":454},[80,2326,2327,2329,2331],{"class":82,"line":376},[80,2328,1654],{"class":618},[80,2330,1565],{"class":618},[80,2332,1247],{"class":214},[80,2334,2335,2338,2341,2344,2346],{"class":82,"line":496},[80,2336,2337],{"class":214},"        _ ",[80,2339,2340],{"class":618},"=",[80,2342,2343],{"class":214}," srv.",[80,2345,1384],{"class":86},[80,2347,1582],{"class":214},[80,2349,2350],{"class":82,"line":501},[80,2351,1681],{"class":214},[80,2353,2354,2357,2360],{"class":82,"line":507},[80,2355,2356],{"class":214},"    t.",[80,2358,2359],{"class":86},"Cleanup",[80,2361,2362],{"class":214},"(srv.Stop)\n",[80,2364,2365],{"class":82,"line":513},[80,2366,455],{"emptyLinePlaceholder":454},[80,2368,2369,2371,2373,2375,2377],{"class":82,"line":519},[80,2370,1903],{"class":214},[80,2372,1256],{"class":618},[80,2374,1322],{"class":214},[80,2376,1910],{"class":86},[80,2378,946],{"class":214},[80,2380,2381,2384,2387,2390,2393],{"class":82,"line":524},[80,2382,2383],{"class":214},"        lis.",[80,2385,2386],{"class":86},"Addr",[80,2388,2389],{"class":214},"().",[80,2391,2392],{"class":86},"String",[80,2394,1037],{"class":214},[80,2396,2397,2399,2401,2403,2405],{"class":82,"line":529},[80,2398,1924],{"class":214},[80,2400,1927],{"class":86},[80,2402,1930],{"class":214},[80,2404,1933],{"class":86},[80,2406,1219],{"class":214},[80,2408,2409],{"class":82,"line":535},[80,2410,1940],{"class":214},[80,2412,2413,2415,2417,2419,2421],{"class":82,"line":541},[80,2414,1124],{"class":618},[80,2416,1282],{"class":214},[80,2418,1285],{"class":618},[80,2420,1148],{"class":119},[80,2422,628],{"class":214},[80,2424,2425,2427,2429],{"class":82,"line":546},[80,2426,2280],{"class":214},[80,2428,1298],{"class":86},[80,2430,1301],{"class":214},[80,2432,2433],{"class":82,"line":551},[80,2434,1168],{"class":214},[80,2436,2437,2439,2441,2443,2445,2448,2450,2452,2454],{"class":82,"line":557},[80,2438,2356],{"class":214},[80,2440,2359],{"class":86},[80,2442,636],{"class":214},[80,2444,925],{"class":618},[80,2446,2447],{"class":214},"() { _ ",[80,2449,2340],{"class":618},[80,2451,1972],{"class":214},[80,2453,1975],{"class":86},[80,2455,2456],{"class":214},"() })\n",[80,2458,2459],{"class":82,"line":563},[80,2460,455],{"emptyLinePlaceholder":454},[80,2462,2463,2465,2467,2469,2471],{"class":82,"line":569},[80,2464,1986],{"class":214},[80,2466,1256],{"class":618},[80,2468,1991],{"class":214},[80,2470,1994],{"class":86},[80,2472,1997],{"class":214},[80,2474,2475,2477,2479,2481,2483,2485,2487,2489,2491,2493,2495,2497,2500,2502,2505,2507],{"class":82,"line":574},[80,2476,2047],{"class":214},[80,2478,1256],{"class":618},[80,2480,2052],{"class":214},[80,2482,943],{"class":86},[80,2484,1481],{"class":214},[80,2486,1484],{"class":86},[80,2488,2021],{"class":214},[80,2490,1343],{"class":618},[80,2492,972],{"class":86},[80,2494,642],{"class":214},[80,2496,654],{"class":86},[80,2498,2499],{"class":214},"{A: ",[80,2501,2075],{"class":119},[80,2503,2504],{"class":214},", B: ",[80,2506,2024],{"class":119},[80,2508,2509],{"class":214},"})\n",[80,2511,2512,2514,2516,2518,2520],{"class":82,"line":579},[80,2513,1124],{"class":618},[80,2515,1282],{"class":214},[80,2517,1285],{"class":618},[80,2519,1148],{"class":119},[80,2521,628],{"class":214},[80,2523,2524,2526,2528],{"class":82,"line":585},[80,2525,2280],{"class":214},[80,2527,1298],{"class":86},[80,2529,1301],{"class":214},[80,2531,2532],{"class":82,"line":591},[80,2533,1168],{"class":214},[80,2535,2536],{"class":82,"line":1087},[80,2537,455],{"emptyLinePlaceholder":454},[80,2539,2540,2542,2545,2547,2549,2551,2554],{"class":82,"line":1102},[80,2541,1124],{"class":618},[80,2543,2544],{"class":214}," resp.",[80,2546,2132],{"class":86},[80,2548,1025],{"class":214},[80,2550,1285],{"class":618},[80,2552,2553],{"class":119}," 42",[80,2555,628],{"class":214},[80,2557,2558,2560,2563,2565,2568,2571,2574,2577,2579],{"class":82,"line":1121},[80,2559,2280],{"class":214},[80,2561,2562],{"class":86},"Fatalf",[80,2564,636],{"class":214},[80,2566,2567],{"class":90},"\"result = ",[80,2569,2570],{"class":119},"%d",[80,2572,2573],{"class":90},", want 42\"",[80,2575,2576],{"class":214},", resp.",[80,2578,2132],{"class":86},[80,2580,2135],{"class":214},[80,2582,2583],{"class":82,"line":1142},[80,2584,1168],{"class":214},[80,2586,2587],{"class":82,"line":1165},[80,2588,493],{"class":214},[15,2590,2591,2592,2595,2596,2598,2599,2601],{},"Инструменты вроде ",[19,2593,2594],{},"grpcurl"," тоже подходят, но это не обычный ",[19,2597,2175],{},": им нужен ",[19,2600,21],{}," файл или server reflection. Для учебного проекта Go-клиент и Go-тест обычно полезнее, потому что сразу проверяют generated client, deadline, status code и маппинг ошибок.",[37,2603],{},[40,2605,2607],{"id":2606},"где-здесь-timeout","Где здесь timeout",[15,2609,2610],{},"Каждый сетевой вызов должен иметь ограничение по времени:",[27,2612,2614],{"className":610,"code":2613,"language":87,"meta":33,"style":33},"ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\ndefer cancel()\n\nresp, err := client.Divide(ctx, &calculatorv1.DivideRequest{\n    Dividend: 10,\n    Divisor:  2,\n})\n",[19,2615,2616,2639,2647,2651,2674,2683,2692],{"__ignoreMap":33},[80,2617,2618,2621,2623,2625,2627,2629,2631,2633,2635,2637],{"class":82,"line":83},[80,2619,2620],{"class":214},"ctx, cancel ",[80,2622,1256],{"class":618},[80,2624,2011],{"class":214},[80,2626,2014],{"class":86},[80,2628,1481],{"class":214},[80,2630,1484],{"class":86},[80,2632,2021],{"class":214},[80,2634,2024],{"class":119},[80,2636,651],{"class":618},[80,2638,2029],{"class":214},[80,2640,2641,2643,2645],{"class":82,"line":97},[80,2642,1492],{"class":618},[80,2644,2036],{"class":86},[80,2646,1328],{"class":214},[80,2648,2649],{"class":82,"line":133},[80,2650,455],{"emptyLinePlaceholder":454},[80,2652,2653,2656,2658,2660,2662,2664,2666,2668,2670,2672],{"class":82,"line":144},[80,2654,2655],{"class":214},"resp, err ",[80,2657,1256],{"class":618},[80,2659,2052],{"class":214},[80,2661,1070],{"class":86},[80,2663,2057],{"class":214},[80,2665,1343],{"class":618},[80,2667,972],{"class":86},[80,2669,642],{"class":214},[80,2671,690],{"class":86},[80,2673,1014],{"class":214},[80,2675,2676,2679,2681],{"class":82,"line":249},[80,2677,2678],{"class":214},"    Dividend: ",[80,2680,1719],{"class":119},[80,2682,961],{"class":214},[80,2684,2685,2688,2690],{"class":82,"line":257},[80,2686,2687],{"class":214},"    Divisor:  ",[80,2689,2024],{"class":119},[80,2691,961],{"class":214},[80,2693,2694],{"class":82,"line":266},[80,2695,2509],{"class":214},[15,2697,2698,2699,2702,2703,2706],{},"Если deadline истечёт, клиент получит gRPC error с кодом ",[19,2700,2701],{},"DeadlineExceeded",". Сервер при этом должен уважать ",[19,2704,2705],{},"ctx.Done()",", особенно если метод делает долгие запросы в БД или внешние API.",[37,2708],{},[40,2710,2712],{"id":2711},"что-читать-в-generated-code","Что читать в generated code",[15,2714,2715,2716,2718],{},"Откройте ",[19,2717,603],{}," и найдите:",[48,2720,2721,2727,2731,2736,2740,2744],{},[51,2722,2723,2726],{},[19,2724,2725],{},"CalculatorServiceClient",";",[51,2728,2729,2726],{},[19,2730,1994],{},[51,2732,2733,2726],{},[19,2734,2735],{},"CalculatorServiceServer",[51,2737,2738,2726],{},[19,2739,1417],{},[51,2741,2742,2726],{},[19,2743,1337],{},[51,2745,2746],{},"handler-функции, через которые gRPC связывает transport и ваш метод.",[15,2748,2749,2750,2753],{},"Это полезно сделать хотя бы один раз. После этого gRPC перестаёт выглядеть как магия: generated code просто вызывает ваш Go-метод с ",[19,2751,2752],{},"context.Context"," и request message.",[37,2755],{},[40,2757,2759],{"id":2758},"практика","Практика",[2761,2762,2763,2774,2777,2783,2788,2798],"ol",{},[51,2764,2765,2766,2769,2770,418,2772,642],{},"Создайте ",[19,2767,2768],{},"CalculatorService"," с методами ",[19,2771,943],{},[19,2773,1070],{},[51,2775,2776],{},"Сгенерируйте Go-код.",[51,2778,2779,2780,642],{},"Реализуйте сервер на порту ",[19,2781,2782],{},":50051",[51,2784,2785,2786,642],{},"Напишите CLI-клиент, который вызывает ",[19,2787,943],{},[51,2789,2790,2791,2793,2794,2797],{},"Добавьте ",[19,2792,1070],{}," и верните ",[19,2795,2796],{},"InvalidArgument"," при делении на ноль.",[51,2799,2800,2801,642],{},"На клиенте обработайте ошибку через ",[19,2802,2803],{},"status.FromError",[37,2805],{},[40,2807,2809],{"id":2808},"типичные-ошибки","Типичные ошибки",[48,2811,2812,2819,2822,2825,2835,2838],{},[51,2813,2814,2815,2818],{},"Редактировать ",[19,2816,2817],{},"*.pb.go"," руками. Эти файлы перегенерируются.",[51,2820,2821],{},"Положить бизнес-логику внутрь generated-кода.",[51,2823,2824],{},"Забыть timeout на клиентском вызове.",[51,2826,2827,2828,2831,2832,642],{},"Возвращать ",[19,2829,2830],{},"fmt.Errorf(\"division by zero\")"," наружу вместо ",[19,2833,2834],{},"status.Error(codes.InvalidArgument, ...)",[51,2836,2837],{},"Использовать insecure credentials в продакшене без понимания инфраструктурной защиты.",[51,2839,2840],{},"Не читать generated interface и пытаться угадать сигнатуры.",[37,2842],{},[40,2844,2846],{"id":2845},"источники","Источники",[48,2848,2849,2858,2865,2872,2878],{},[51,2850,2851],{},[2852,2853,2857],"a",{"href":2854,"rel":2855},"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Fquickstart\u002Fgo\u002F",[2856],"nofollow","gRPC Go Quick Start",[51,2859,2860],{},[2852,2861,2864],{"href":2862,"rel":2863},"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Flanguages\u002Fgo\u002Fbasics\u002F",[2856],"gRPC Go Basics",[51,2866,2867],{},[2852,2868,2871],{"href":2869,"rel":2870},"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Flanguages\u002Fgo\u002Fgenerated-code\u002F",[2856],"gRPC Go Generated Code Reference",[51,2873,2874],{},[2852,2875,862],{"href":2876,"rel":2877},"https:\u002F\u002Fpkg.go.dev\u002Fgoogle.golang.org\u002Fgrpc",[2856],[51,2879,2880],{},[2852,2881,1880],{"href":2882,"rel":2883},"https:\u002F\u002Fpkg.go.dev\u002Fgoogle.golang.org\u002Fgrpc\u002Fcredentials\u002Finsecure",[2856],[37,2885],{},[40,2887,2889],{"id":2888},"интерактивная-практика","Интерактивная практика",[2891,2892,2894,2897,2922],"quiz",{"answer":2024,"id":2893,"xp":1719},"rpc-go-q1",[15,2895,2896],{},"Какие файлы нельзя редактировать руками после генерации gRPC-кода?",[2898,2899,2900],"template",{"v-slot:options":33},[48,2901,2902,2907,2913,2917],{},[51,2903,2904],{},[19,2905,2906],{},"cmd\u002Fserver\u002Fmain.go",[51,2908,2909,418,2911],{},[19,2910,2817],{},[19,2912,720],{},[51,2914,2915],{},[19,2916,758],{},[51,2918,2919],{},[19,2920,2921],{},"README.md",[2898,2923,2924],{"v-slot:explanation":33},[15,2925,2926,2927,2929,2930,642],{},"Generated files перегенерируются из ",[19,2928,21],{},". Бизнесовую реализацию пишут рядом, а не внутри ",[19,2931,2817],{},[2933,2934,2938,2941,3081],"predict",{"answer":2935,"id":2936,"xp":2937},"InvalidArgument\\nOK","rpc-go-p1","15",[15,2939,2940],{},"Что выведет программа?",[2898,2942,2943],{"v-slot:code":33},[27,2944,2946],{"className":610,"code":2945,"language":87,"meta":33,"style":33},"package main\n\nimport \"fmt\"\n\nfunc divideCode(divisor int) string {\n    if divisor == 0 {\n        return \"InvalidArgument\"\n    }\n    return \"OK\"\n}\n\nfunc main() {\n    fmt.Println(divideCode(0))\n    fmt.Println(divideCode(2))\n}\n",[19,2947,2948,2954,2958,2969,2973,2995,3008,3015,3019,3026,3030,3034,3042,3061,3077],{"__ignoreMap":33},[80,2949,2950,2952],{"class":82,"line":83},[80,2951,795],{"class":618},[80,2953,798],{"class":86},[80,2955,2956],{"class":82,"line":97},[80,2957,455],{"emptyLinePlaceholder":454},[80,2959,2960,2962,2965,2967],{"class":82,"line":133},[80,2961,807],{"class":618},[80,2963,2964],{"class":90}," \"",[80,2966,1832],{"class":86},[80,2968,820],{"class":90},[80,2970,2971],{"class":82,"line":144},[80,2972,455],{"emptyLinePlaceholder":454},[80,2974,2975,2977,2980,2982,2985,2988,2990,2993],{"class":82,"line":249},[80,2976,925],{"class":618},[80,2978,2979],{"class":86}," divideCode",[80,2981,636],{"class":214},[80,2983,2984],{"class":931},"divisor",[80,2986,2987],{"class":618}," int",[80,2989,940],{"class":214},[80,2991,2992],{"class":618},"string",[80,2994,628],{"class":214},[80,2996,2997,2999,3002,3004,3006],{"class":82,"line":257},[80,2998,1124],{"class":618},[80,3000,3001],{"class":214}," divisor ",[80,3003,1134],{"class":618},[80,3005,1137],{"class":119},[80,3007,628],{"class":214},[80,3009,3010,3012],{"class":82,"line":266},[80,3011,1145],{"class":618},[80,3013,3014],{"class":90}," \"InvalidArgument\"\n",[80,3016,3017],{"class":82,"line":274},[80,3018,1168],{"class":214},[80,3020,3021,3023],{"class":82,"line":281},[80,3022,1002],{"class":618},[80,3024,3025],{"class":90}," \"OK\"\n",[80,3027,3028],{"class":82,"line":376},[80,3029,493],{"class":214},[80,3031,3032],{"class":82,"line":496},[80,3033,455],{"emptyLinePlaceholder":454},[80,3035,3036,3038,3040],{"class":82,"line":501},[80,3037,925],{"class":618},[80,3039,1244],{"class":86},[80,3041,1247],{"class":214},[80,3043,3044,3046,3048,3050,3053,3055,3058],{"class":82,"line":507},[80,3045,2124],{"class":214},[80,3047,1362],{"class":86},[80,3049,636],{"class":214},[80,3051,3052],{"class":86},"divideCode",[80,3054,636],{"class":214},[80,3056,3057],{"class":119},"0",[80,3059,3060],{"class":214},"))\n",[80,3062,3063,3065,3067,3069,3071,3073,3075],{"class":82,"line":513},[80,3064,2124],{"class":214},[80,3066,1362],{"class":86},[80,3068,636],{"class":214},[80,3070,3052],{"class":86},[80,3072,636],{"class":214},[80,3074,2024],{"class":119},[80,3076,3060],{"class":214},[80,3078,3079],{"class":82,"line":519},[80,3080,493],{"class":214},[2898,3082,3083],{"v-slot:hint":33},[15,3084,3085,3086,3089,3090,642],{},"Ошибка валидации аргумента клиента — не ",[19,3087,3088],{},"Internal",", а ",[19,3091,2796],{},[3093,3094,3098,3116,3264],"code-task",{"expected":3095,"id":3096,"xp":3097},"timeout\\nok\\nerror","rpc-go-ct1","20",[15,3099,3100,3101,3104,3105,3108,3109,3112,3113,642],{},"Реализуй ",[19,3102,3103],{},"ClientCallReview",": для client call без deadline верни ",[19,3106,3107],{},"\"timeout\"",", для успешного вызова ",[19,3110,3111],{},"\"ok\"",", для ошибки ",[19,3114,3115],{},"\"error\"",[2898,3117,3118],{"v-slot:template":33},[27,3119,3121],{"className":610,"code":3120,"language":87,"meta":33,"style":33},"package main\n\nimport \"fmt\"\n\nfunc ClientCallReview(hasDeadline bool, err bool) string {\n    return \"todo\"\n}\n\nfunc main() {\n    fmt.Println(ClientCallReview(false, false))\n    fmt.Println(ClientCallReview(true, false))\n    fmt.Println(ClientCallReview(true, true))\n}\n",[19,3122,3123,3129,3133,3143,3147,3175,3182,3186,3190,3198,3219,3240,3260],{"__ignoreMap":33},[80,3124,3125,3127],{"class":82,"line":83},[80,3126,795],{"class":618},[80,3128,798],{"class":86},[80,3130,3131],{"class":82,"line":97},[80,3132,455],{"emptyLinePlaceholder":454},[80,3134,3135,3137,3139,3141],{"class":82,"line":133},[80,3136,807],{"class":618},[80,3138,2964],{"class":90},[80,3140,1832],{"class":86},[80,3142,820],{"class":90},[80,3144,3145],{"class":82,"line":144},[80,3146,455],{"emptyLinePlaceholder":454},[80,3148,3149,3151,3154,3156,3159,3162,3164,3167,3169,3171,3173],{"class":82,"line":249},[80,3150,925],{"class":618},[80,3152,3153],{"class":86}," ClientCallReview",[80,3155,636],{"class":214},[80,3157,3158],{"class":931},"hasDeadline",[80,3160,3161],{"class":618}," bool",[80,3163,648],{"class":214},[80,3165,3166],{"class":931},"err",[80,3168,3161],{"class":618},[80,3170,940],{"class":214},[80,3172,2992],{"class":618},[80,3174,628],{"class":214},[80,3176,3177,3179],{"class":82,"line":257},[80,3178,1002],{"class":618},[80,3180,3181],{"class":90}," \"todo\"\n",[80,3183,3184],{"class":82,"line":266},[80,3185,493],{"class":214},[80,3187,3188],{"class":82,"line":274},[80,3189,455],{"emptyLinePlaceholder":454},[80,3191,3192,3194,3196],{"class":82,"line":281},[80,3193,925],{"class":618},[80,3195,1244],{"class":86},[80,3197,1247],{"class":214},[80,3199,3200,3202,3204,3206,3208,3210,3213,3215,3217],{"class":82,"line":376},[80,3201,2124],{"class":214},[80,3203,1362],{"class":86},[80,3205,636],{"class":214},[80,3207,3103],{"class":86},[80,3209,636],{"class":214},[80,3211,3212],{"class":119},"false",[80,3214,648],{"class":214},[80,3216,3212],{"class":119},[80,3218,3060],{"class":214},[80,3220,3221,3223,3225,3227,3229,3231,3234,3236,3238],{"class":82,"line":496},[80,3222,2124],{"class":214},[80,3224,1362],{"class":86},[80,3226,636],{"class":214},[80,3228,3103],{"class":86},[80,3230,636],{"class":214},[80,3232,3233],{"class":119},"true",[80,3235,648],{"class":214},[80,3237,3212],{"class":119},[80,3239,3060],{"class":214},[80,3241,3242,3244,3246,3248,3250,3252,3254,3256,3258],{"class":82,"line":501},[80,3243,2124],{"class":214},[80,3245,1362],{"class":86},[80,3247,636],{"class":214},[80,3249,3103],{"class":86},[80,3251,636],{"class":214},[80,3253,3233],{"class":119},[80,3255,648],{"class":214},[80,3257,3233],{"class":119},[80,3259,3060],{"class":214},[80,3261,3262],{"class":82,"line":507},[80,3263,493],{"class":214},[2898,3265,3266],{"v-slot:hints":33},[48,3267,3268,3271,3274],{},[51,3269,3270],{},"Сначала проверь deadline",[51,3272,3273],{},"Потом обработай ошибку",[51,3275,3276,3277],{},"Только после этого возвращай ",[19,3278,3111],{},[3280,3281,3282],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":33,"searchDepth":97,"depth":97,"links":3284},[3285,3288,3289,3290,3293,3294,3296,3297,3298,3299,3300,3301],{"id":42,"depth":97,"text":43,"children":3286},[3287],{"id":160,"depth":133,"text":161},{"id":436,"depth":97,"text":437},{"id":726,"depth":97,"text":727},{"id":783,"depth":97,"text":784,"children":3291},[3292],{"id":1448,"depth":133,"text":1449},{"id":1795,"depth":97,"text":1796},{"id":2171,"depth":97,"text":3295},"Локальная проверка без curl",{"id":2606,"depth":97,"text":2607},{"id":2711,"depth":97,"text":2712},{"id":2758,"depth":97,"text":2759},{"id":2808,"depth":97,"text":2809},{"id":2845,"depth":97,"text":2846},{"id":2888,"depth":97,"text":2889},1781022064757]