Создание обратного прокси-сервера (gRPC-Gateway)
В этой статье мы рассмотрим создание API со следующим шагом реализации обратного прокси.
Мы будем использовать docker и docker-compose, поэтому я рекомендую сначала установить docker и docker-compose и убедиться, что вы можете запускать контейнеры.
Перейдите к вашему GOPATH
, если вы не знаете, какой у вас каталог пути, вы можете запустить echo $GOPATH
, и он напечатает правильный путь. Там создайте src/github/com/yaairfernando
структуру папок, заменив последний каталог вашим дескриптором GitHub. Как только вы окажетесь там, создайте следующую структуру проекта:
mkdir socialMedia
cd socialMedia
touch docker-compose.yaml
Давайте начнем с определения yaml-файла docker-compose.
version: '2.4'
services:
proxy:
container_name: 'proxy'
image: 'sma:proxy'
build:
context: ./proxy
args:
- GITHUB_ACCESS_TOKEN=${GITHUB_ACCESS_TOKEN}
command: bash -c "./grpc-gateway"
ports:
- '9094:8081'
environment:
- GRPC_URL=sma:8080
- REST_PORT=8081
В приведенном выше файле yaml мы определяем proxy
службу, из конфигурации этой службы мы видим, что имя контейнера — proxy
, а образ, который будет использоваться sma:proxy
. Если образ докера не существует, он создаст его из ./proxy
папку, мы также передаем в качестве аргументов токен доступа GitHub, это необходимо только в том случае, если вы создали частное репо для дизайна API.
Вам нужно будет передать токен доступа GitHub к образу докера, чтобы получить доступ к частным репозиториям из файла докера.
Мы также указываем команду и две переменные среды для запуска: одну, чтобы указать оставшийся порт, на котором будет работать сервер, и URL-адрес grpc, который указывает на службу, которую прокси-сервер будет передавать запросы.
Мы также указываем, что порт, который эта служба будет предоставлять, будет 9094
внутренним портом 8081
.
Давайте теперь создадим папку прокси и main.go
файл:
mkdir proxy
cd proxy
touch main.go
Внутри папки прокси мы запустим эту команду, чтобы создать модуль go
go mod init github.com/yaairfernando/proxy
Это создаст go.mod
файл в папке прокси.
module github.com/yaairfernando/proxy
go 1.18
Файл main.go
выглядит так:
package main
import (
"os"
"github.com/yaairfernando/proxy/server"
)
var (
grpcURL = os.Getenv("GRPC_URL")
restPort = os.Getenv("REST_PORT")
)
func main() {
srv := server.New(grpcURL, restPort)
srv.Start()
}
Основная функция только инициализирует новый сервер, передавая grpcURL
, restPort
и вызывая метод запуска на экземпляре сервера.
Давайте посмотрим, как выглядит файл сервера. Создайте папку сервера и server.go
файл.
mkdir server
cd server
touch server.go
package server
import (
"context"
"fmt"
"log"
"net/http"
"github.com/felixge/httpsnoop"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/yaairfernando/sma/protos/src/go/sma"
"google.golang.org/grpc"
)
type Server struct {
GrpcURL string
RestPort string
srv *http.Server
}
func New(grpcURL, restPort string) *Server {
return &Server{
GrpcURL: grpcURL,
RestPort: restPort,
}
}
func (s *Server) Start() {
grpcURL := GetGrpcURL(s.GrpcURL)
restPort := GetRestPort(s.RestPort)
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{}))
handler := s.handlerWithLogger(mux)
opts := []grpc.DialOption{grpc.WithInsecure()}
err := s.registerHandlers(ctx, mux, grpcURL, opts)
if err != nil {
log.Fatal(err)
}
s.srv = &http.Server{
Handler: handler,
Addr: fmt.Sprintf(":%s", restPort),
}
s.listenAndServe()
}
func (s *Server) registerHandlers(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
err = sma.RegisterPostsHandlerFromEndpoint(ctx, mux, endpoint, opts)
return
}
func (s *Server) handlerWithLogger(handler http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
m := httpsnoop.CaptureMetrics(handler, writer, request)
log.Printf("http[%d]-- %s -- %s -- %s\n", m.Code, m.Duration, request.URL.Path, request.Method)
})
}
func (s *Server) listenAndServe() {
err := s.srv.ListenAndServe()
if err != nil {
log.Fatal(err)
}
}
В этом файле сервера есть вся логика для регистрации обработчика Posts
ресурса, добавления простого регистратора, создания HTTP-сервера и прослушивания указанного порта.
- В строке 36 мы создаем новый серверный мультиплексор
- В строке 38 мы обернули этот сервер простым регистратором.
- В строке 40 регистрируем все обработчики. В нашем случае у нас есть только один для
Posts
ресурса на данный момент - Наконец, в строках 45–49 мы создаем HTTP-сервер, передающий обработчик, и начинаем слушать
Как видите, некоторые вспомогательные функции в этом файле не определены. Я добавил их в utils
файл.
package server
func GetGrpcURL(url string) string {
return LoadEnv(url, "0.0.0.0:8080")
}
func GetRestPort(port string) string {
return LoadEnv(port, "3000")
}
func LoadEnv(attr, def string) string {
if len(attr) > 0 {
return attr
}
return def
}
Хорошо, давайте теперь создадим Dockerfile
:
FROM golang:1.18-alpine AS builder
ARG GITHUB_ACCESS_TOKEN
ENV GO111MODULE=on \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64 \
GOPRIVATE=github.com/yaairfernando \
GOPROXY=direct
RUN apk --no-cache add git bash && \
addgroup -S sma-grpc-proxy && \
adduser -SG sma-grpc-proxy sma-grpc-proxy
WORKDIR $GOPATH/github.com/yaairfernando/sma-grpc-proxy
RUN git config --global url."https://${GITHUB_ACCESS_TOKEN}:x-oauth-basic@github.com/yaairfernando".insteadOf "https://github.com/yaairfernando"
COPY go.mod .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags='-w -s' -o /usr/bin/sma-grpc-proxy
FROM scratch
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /usr/bin/sma-grpc-proxy /usr/bin/sma-grpc-proxy
USER sma-grpc-proxy
EXPOSE 8081
ENTRYPOINT ["/usr/bin/sma-grpc-proxy"]
Я не буду углубляться в то, что делает этот файл докера, но, по сути, он использует образ Golang в качестве сборщика, устанавливает некоторые переменные среды go, устанавливает рабочий каталог и копирует go.mod
файл. После этого он загружает все зависимости, копирует остальные файлы и, наконец, устанавливает точку входа.
Это go.mod
файл со всеми используемыми пакетами.
module github.com/yaairfernando/proxy
go 1.18
require (
github.com/felixge/httpsnoop v1.0.3
github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.0
github.com/yaairfernando/sma v0.1.0
google.golang.org/grpc v1.46.2
)
require (
github.com/golang/protobuf v1.5.2 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 // indirect
google.golang.org/protobuf v1.28.0 // indirect
)
Это окончательная структура папок для проекта:
Мы почти закончили. Теперь давайте загрузим нужные нам пакеты go, выполнив следующие команды в папке прокси:
go mod download
go mod tidy
Как видите, для sma
пакета мы указали, что нам нужна версия v0.1.0
из этого пакета, но когда мы создавали дизайн API, мы не создавали никаких версий. Давайте сделаем это.
Создать тег Git
Вернитесь туда, где у вас есть sma
проект, и давайте создадим тег:
git tag v0.1.0
И отправляем в репозиторий:
git push origin v0.1.0
Теперь, вернувшись в proxy
папку, запустите эту команду, чтобы загрузить эту версию sma
пакета:
go get github.com/yaairfernando/sma@v0.1.0
Приведенная выше команда загрузит sma
пакет с указанной версией.
Сборка Docker-сервиса
Теперь мы можем создать прокси-сервис, запустив:
docker-compose up --build -d proxy
При выполнении этой команды вы получите некоторые ошибки, говорящие о загрузке некоторых пакетов, которые мы указали в go.mod
файле. Просто загрузите пакеты, запустив go mod download package_path
. И повторно запустите команду docker-compose.
После завершения сборки мы должны увидеть работающий контейнер. Отправить POST-запрос на http://localhost:9094/v1/posts
пока не получится, но лог можно посмотреть.
Мы реализовали обратный прокси.